mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-11-18 10:06:51 +01:00
graph+lnd: add NetworkStats to GraphSource interface
so that the external graph source can be used to query network information rather than depending on the local graph DB.
This commit is contained in:
37
graph/db/models/stats.go
Normal file
37
graph/db/models/stats.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package models
|
||||
|
||||
import "github.com/btcsuite/btcd/btcutil"
|
||||
|
||||
// NetworkStats represents various statistics about the state of the Lightning
|
||||
// network graph.
|
||||
type NetworkStats struct {
|
||||
// Diameter is the diameter of the graph, which is the length of the
|
||||
// longest shortest path between any two nodes in the graph.
|
||||
Diameter uint32
|
||||
|
||||
// MaxChanOut is the maximum number of outgoing channels from a single
|
||||
// node.
|
||||
MaxChanOut uint32
|
||||
|
||||
// NumNodes is the total number of nodes in the graph.
|
||||
NumNodes uint32
|
||||
|
||||
// NumChannels is the total number of channels in the graph.
|
||||
NumChannels uint32
|
||||
|
||||
// TotalNetworkCapacity is the total capacity of all channels in the
|
||||
// graph.
|
||||
TotalNetworkCapacity btcutil.Amount
|
||||
|
||||
// MinChanSize is the smallest channel size in the graph.
|
||||
MinChanSize btcutil.Amount
|
||||
|
||||
// MaxChanSize is the largest channel size in the graph.
|
||||
MaxChanSize btcutil.Amount
|
||||
|
||||
// MedianChanSize is the median channel size in the graph.
|
||||
MedianChanSize btcutil.Amount
|
||||
|
||||
// NumZombies is the number of zombie channels in the graph.
|
||||
NumZombies uint64
|
||||
}
|
||||
@@ -3,10 +3,12 @@ package sources
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightningnetwork/lnd/autopilot"
|
||||
"github.com/lightningnetwork/lnd/discovery"
|
||||
@@ -231,6 +233,133 @@ func (s *DBSource) GraphBootstrapper(_ context.Context) (
|
||||
return discovery.NewGraphBootstrapper(chanGraph)
|
||||
}
|
||||
|
||||
// NetworkStats returns statistics concerning the current state of the known
|
||||
// channel graph within the network.
|
||||
//
|
||||
// NOTE: this is part of the GraphSource interface.
|
||||
func (s *DBSource) NetworkStats(_ context.Context) (*models.NetworkStats,
|
||||
error) {
|
||||
|
||||
var (
|
||||
numNodes uint32
|
||||
numChannels uint32
|
||||
maxChanOut uint32
|
||||
totalNetworkCapacity btcutil.Amount
|
||||
minChannelSize btcutil.Amount = math.MaxInt64
|
||||
maxChannelSize btcutil.Amount
|
||||
medianChanSize btcutil.Amount
|
||||
)
|
||||
|
||||
// We'll use this map to de-duplicate channels during our traversal.
|
||||
// This is needed since channels are directional, so there will be two
|
||||
// edges for each channel within the graph.
|
||||
seenChans := make(map[uint64]struct{})
|
||||
|
||||
// We also keep a list of all encountered capacities, in order to
|
||||
// calculate the median channel size.
|
||||
var allChans []btcutil.Amount
|
||||
|
||||
// We'll run through all the known nodes in the within our view of the
|
||||
// network, tallying up the total number of nodes, and also gathering
|
||||
// each node so we can measure the graph diameter and degree stats
|
||||
// below.
|
||||
err := s.db.ForEachNodeCached(func(node route.Vertex,
|
||||
edges map[uint64]*graphdb.DirectedChannel) error {
|
||||
|
||||
// Increment the total number of nodes with each iteration.
|
||||
numNodes++
|
||||
|
||||
// For each channel we'll compute the out degree of each node,
|
||||
// and also update our running tallies of the min/max channel
|
||||
// capacity, as well as the total channel capacity. We pass
|
||||
// through the DB transaction from the outer view so we can
|
||||
// re-use it within this inner view.
|
||||
var outDegree uint32
|
||||
for _, edge := range edges {
|
||||
// Bump up the out degree for this node for each
|
||||
// channel encountered.
|
||||
outDegree++
|
||||
|
||||
// If we've already seen this channel, then we'll
|
||||
// return early to ensure that we don't double-count
|
||||
// stats.
|
||||
if _, ok := seenChans[edge.ChannelID]; ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Compare the capacity of this channel against the
|
||||
// running min/max to see if we should update the
|
||||
// extrema.
|
||||
chanCapacity := edge.Capacity
|
||||
if chanCapacity < minChannelSize {
|
||||
minChannelSize = chanCapacity
|
||||
}
|
||||
if chanCapacity > maxChannelSize {
|
||||
maxChannelSize = chanCapacity
|
||||
}
|
||||
|
||||
// Accumulate the total capacity of this channel to the
|
||||
// network wide-capacity.
|
||||
totalNetworkCapacity += chanCapacity
|
||||
|
||||
numChannels++
|
||||
|
||||
seenChans[edge.ChannelID] = struct{}{}
|
||||
allChans = append(allChans, edge.Capacity)
|
||||
}
|
||||
|
||||
// Finally, if the out degree of this node is greater than what
|
||||
// we've seen so far, update the maxChanOut variable.
|
||||
if outDegree > maxChanOut {
|
||||
maxChanOut = outDegree
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Find the median.
|
||||
medianChanSize = autopilot.Median(allChans)
|
||||
|
||||
// If we don't have any channels, then reset the minChannelSize to zero
|
||||
// to avoid outputting NaN in encoded JSON.
|
||||
if numChannels == 0 {
|
||||
minChannelSize = 0
|
||||
}
|
||||
|
||||
// Graph diameter.
|
||||
channelGraph := autopilot.ChannelGraphFromCachedDatabase(s.db)
|
||||
simpleGraph, err := autopilot.NewSimpleGraph(channelGraph)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
start := time.Now()
|
||||
diameter := simpleGraph.DiameterRadialCutoff()
|
||||
|
||||
log.Infof("Elapsed time for diameter (%d) calculation: %v", diameter,
|
||||
time.Since(start))
|
||||
|
||||
// Query the graph for the current number of zombie channels.
|
||||
numZombies, err := s.db.NumZombies()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &models.NetworkStats{
|
||||
Diameter: diameter,
|
||||
MaxChanOut: maxChanOut,
|
||||
NumNodes: numNodes,
|
||||
NumChannels: numChannels,
|
||||
TotalNetworkCapacity: totalNetworkCapacity,
|
||||
MinChanSize: minChannelSize,
|
||||
MaxChanSize: maxChannelSize,
|
||||
MedianChanSize: medianChanSize,
|
||||
NumZombies: numZombies,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// kvdbRTx is an implementation of graphdb.RTx backed by a KVDB database read
|
||||
// transaction.
|
||||
type kvdbRTx struct {
|
||||
|
||||
@@ -74,4 +74,8 @@ type GraphSource interface {
|
||||
// used to discover new peers to connect to.
|
||||
GraphBootstrapper(ctx context.Context) (
|
||||
discovery.NetworkPeerBootstrapper, error)
|
||||
|
||||
// NetworkStats returns statistics concerning the current state of the
|
||||
// known channel graph within the network.
|
||||
NetworkStats(ctx context.Context) (*models.NetworkStats, error)
|
||||
}
|
||||
|
||||
31
graph/sources/log.go
Normal file
31
graph/sources/log.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package sources
|
||||
|
||||
import (
|
||||
"github.com/btcsuite/btclog/v2"
|
||||
"github.com/lightningnetwork/lnd/build"
|
||||
)
|
||||
|
||||
// log is a logger that is initialized with no output filters. This means the
|
||||
// package will not perform any logging by default until the caller requests
|
||||
// it.
|
||||
var log btclog.Logger
|
||||
|
||||
const Subsystem = "GRSR"
|
||||
|
||||
// The default amount of logging is none.
|
||||
func init() {
|
||||
UseLogger(build.NewSubLogger(Subsystem, nil))
|
||||
}
|
||||
|
||||
// DisableLog disables all library log output. Logging output is disabled by
|
||||
// default until UseLogger is called.
|
||||
func DisableLog() {
|
||||
UseLogger(btclog.Disabled)
|
||||
}
|
||||
|
||||
// UseLogger uses a specified Logger to output package logging info. This
|
||||
// should be used in preference to SetLogWriter if the caller is also using
|
||||
// btclog.
|
||||
func UseLogger(logger btclog.Logger) {
|
||||
log = logger
|
||||
}
|
||||
2
log.go
2
log.go
@@ -22,6 +22,7 @@ import (
|
||||
"github.com/lightningnetwork/lnd/funding"
|
||||
"github.com/lightningnetwork/lnd/graph"
|
||||
graphdb "github.com/lightningnetwork/lnd/graph/db"
|
||||
"github.com/lightningnetwork/lnd/graph/sources"
|
||||
"github.com/lightningnetwork/lnd/healthcheck"
|
||||
"github.com/lightningnetwork/lnd/htlcswitch"
|
||||
"github.com/lightningnetwork/lnd/invoices"
|
||||
@@ -196,6 +197,7 @@ func SetupLoggers(root *build.SubLoggerManager, interceptor signal.Interceptor)
|
||||
root, blindedpath.Subsystem, interceptor, blindedpath.UseLogger,
|
||||
)
|
||||
AddV1SubLogger(root, graphdb.Subsystem, interceptor, graphdb.UseLogger)
|
||||
AddSubLogger(root, sources.Subsystem, interceptor, sources.UseLogger)
|
||||
}
|
||||
|
||||
// AddSubLogger is a helper method to conveniently create and register the
|
||||
|
||||
132
rpcserver.go
132
rpcserver.go
@@ -6903,134 +6903,34 @@ func (r *rpcServer) QueryRoutes(ctx context.Context,
|
||||
func (r *rpcServer) GetNetworkInfo(ctx context.Context,
|
||||
_ *lnrpc.NetworkInfoRequest) (*lnrpc.NetworkInfo, error) {
|
||||
|
||||
graph := r.server.graphDB
|
||||
graph := r.server.graphSource
|
||||
|
||||
var (
|
||||
numNodes uint32
|
||||
numChannels uint32
|
||||
maxChanOut uint32
|
||||
totalNetworkCapacity btcutil.Amount
|
||||
minChannelSize btcutil.Amount = math.MaxInt64
|
||||
maxChannelSize btcutil.Amount
|
||||
medianChanSize btcutil.Amount
|
||||
)
|
||||
|
||||
// We'll use this map to de-duplicate channels during our traversal.
|
||||
// This is needed since channels are directional, so there will be two
|
||||
// edges for each channel within the graph.
|
||||
seenChans := make(map[uint64]struct{})
|
||||
|
||||
// We also keep a list of all encountered capacities, in order to
|
||||
// calculate the median channel size.
|
||||
var allChans []btcutil.Amount
|
||||
|
||||
// We'll run through all the known nodes in the within our view of the
|
||||
// network, tallying up the total number of nodes, and also gathering
|
||||
// each node so we can measure the graph diameter and degree stats
|
||||
// below.
|
||||
err := graph.ForEachNodeCached(func(node route.Vertex,
|
||||
edges map[uint64]*graphdb.DirectedChannel) error {
|
||||
|
||||
// Increment the total number of nodes with each iteration.
|
||||
numNodes++
|
||||
|
||||
// For each channel we'll compute the out degree of each node,
|
||||
// and also update our running tallies of the min/max channel
|
||||
// capacity, as well as the total channel capacity. We pass
|
||||
// through the db transaction from the outer view so we can
|
||||
// re-use it within this inner view.
|
||||
var outDegree uint32
|
||||
for _, edge := range edges {
|
||||
// Bump up the out degree for this node for each
|
||||
// channel encountered.
|
||||
outDegree++
|
||||
|
||||
// If we've already seen this channel, then we'll
|
||||
// return early to ensure that we don't double-count
|
||||
// stats.
|
||||
if _, ok := seenChans[edge.ChannelID]; ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Compare the capacity of this channel against the
|
||||
// running min/max to see if we should update the
|
||||
// extrema.
|
||||
chanCapacity := edge.Capacity
|
||||
if chanCapacity < minChannelSize {
|
||||
minChannelSize = chanCapacity
|
||||
}
|
||||
if chanCapacity > maxChannelSize {
|
||||
maxChannelSize = chanCapacity
|
||||
}
|
||||
|
||||
// Accumulate the total capacity of this channel to the
|
||||
// network wide-capacity.
|
||||
totalNetworkCapacity += chanCapacity
|
||||
|
||||
numChannels++
|
||||
|
||||
seenChans[edge.ChannelID] = struct{}{}
|
||||
allChans = append(allChans, edge.Capacity)
|
||||
}
|
||||
|
||||
// Finally, if the out degree of this node is greater than what
|
||||
// we've seen so far, update the maxChanOut variable.
|
||||
if outDegree > maxChanOut {
|
||||
maxChanOut = outDegree
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
stats, err := graph.NetworkStats(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Query the graph for the current number of zombie channels.
|
||||
numZombies, err := graph.NumZombies()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Find the median.
|
||||
medianChanSize = autopilot.Median(allChans)
|
||||
|
||||
// If we don't have any channels, then reset the minChannelSize to zero
|
||||
// to avoid outputting NaN in encoded JSON.
|
||||
if numChannels == 0 {
|
||||
minChannelSize = 0
|
||||
}
|
||||
|
||||
// Graph diameter.
|
||||
channelGraph := autopilot.ChannelGraphFromCachedDatabase(graph)
|
||||
simpleGraph, err := autopilot.NewSimpleGraph(channelGraph)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
start := time.Now()
|
||||
diameter := simpleGraph.DiameterRadialCutoff()
|
||||
rpcsLog.Infof("elapsed time for diameter (%d) calculation: %v", diameter,
|
||||
time.Since(start))
|
||||
|
||||
// TODO(roasbeef): also add oldest channel?
|
||||
netInfo := &lnrpc.NetworkInfo{
|
||||
GraphDiameter: diameter,
|
||||
MaxOutDegree: maxChanOut,
|
||||
AvgOutDegree: float64(2*numChannels) / float64(numNodes),
|
||||
NumNodes: numNodes,
|
||||
NumChannels: numChannels,
|
||||
TotalNetworkCapacity: int64(totalNetworkCapacity),
|
||||
AvgChannelSize: float64(totalNetworkCapacity) / float64(numChannels),
|
||||
|
||||
MinChannelSize: int64(minChannelSize),
|
||||
MaxChannelSize: int64(maxChannelSize),
|
||||
MedianChannelSizeSat: int64(medianChanSize),
|
||||
NumZombieChans: numZombies,
|
||||
GraphDiameter: stats.Diameter,
|
||||
MaxOutDegree: stats.MaxChanOut,
|
||||
AvgOutDegree: float64(2*stats.NumChannels) /
|
||||
float64(stats.NumNodes),
|
||||
NumNodes: stats.NumNodes,
|
||||
NumChannels: stats.NumChannels,
|
||||
TotalNetworkCapacity: int64(stats.TotalNetworkCapacity),
|
||||
AvgChannelSize: float64(stats.TotalNetworkCapacity) /
|
||||
float64(stats.NumChannels),
|
||||
MinChannelSize: int64(stats.MinChanSize),
|
||||
MaxChannelSize: int64(stats.MaxChanSize),
|
||||
MedianChannelSizeSat: int64(stats.MedianChanSize),
|
||||
NumZombieChans: stats.NumZombies,
|
||||
}
|
||||
|
||||
// Similarly, if we don't have any channels, then we'll also set the
|
||||
// average channel size to zero in order to avoid weird JSON encoding
|
||||
// outputs.
|
||||
if numChannels == 0 {
|
||||
if stats.NumChannels == 0 {
|
||||
netInfo.AvgChannelSize = 0
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user