graph+lnd: add BetweennessCentrality to GraphSource interface

So that the calcuation is abstracted behind the interface and not
necessarily dependent on LND's local channel graph.
This commit is contained in:
Elle Mouton 2024-11-12 09:14:12 +02:00
parent 80070618a7
commit f36fbd0e45
No known key found for this signature in database
GPG Key ID: D7D916376026F177
4 changed files with 75 additions and 32 deletions

View File

@ -35,3 +35,13 @@ type NetworkStats struct {
// NumZombies is the number of zombie channels in the graph.
NumZombies uint64
}
// BetweennessCentrality represents the betweenness centrality of a node in the
// graph.
type BetweennessCentrality struct {
// Normalized is the normalized betweenness centrality of a node.
Normalized float64
// NonNormalized is the non-normalized betweenness centrality of a node.
NonNormalized float64
}

View File

@ -5,6 +5,7 @@ import (
"fmt"
"math"
"net"
"runtime"
"time"
"github.com/btcsuite/btcd/btcec/v2"
@ -360,6 +361,47 @@ func (s *DBSource) NetworkStats(_ context.Context) (*models.NetworkStats,
}, nil
}
// BetweennessCentrality computes the normalised and non-normalised betweenness
// centrality for each node in the graph.
//
// NOTE: this is part of the GraphSource interface.
func (s *DBSource) BetweennessCentrality(_ context.Context) (
map[autopilot.NodeID]*models.BetweennessCentrality, error) {
channelGraph := autopilot.ChannelGraphFromDatabase(s.db)
centralityMetric, err := autopilot.NewBetweennessCentralityMetric(
runtime.NumCPU(),
)
if err != nil {
return nil, err
}
if err := centralityMetric.Refresh(channelGraph); err != nil {
return nil, err
}
centrality := make(map[autopilot.NodeID]*models.BetweennessCentrality)
for nodeID, val := range centralityMetric.GetMetric(true) {
centrality[nodeID] = &models.BetweennessCentrality{
Normalized: val,
}
}
for nodeID, val := range centralityMetric.GetMetric(false) {
if _, ok := centrality[nodeID]; !ok {
centrality[nodeID] = &models.BetweennessCentrality{
Normalized: val,
}
continue
}
centrality[nodeID].NonNormalized = val
}
return centrality, nil
}
// kvdbRTx is an implementation of graphdb.RTx backed by a KVDB database read
// transaction.
type kvdbRTx struct {

View File

@ -5,6 +5,7 @@ import (
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/autopilot"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/discovery"
"github.com/lightningnetwork/lnd/graph/db/models"
@ -16,6 +17,8 @@ import (
// GraphSource defines the read-only graph interface required by LND for graph
// related queries.
//
//nolint:interfacebloat
type GraphSource interface {
session.ReadOnlyGraph
invoicesrpc.GraphSource
@ -78,4 +81,9 @@ type GraphSource interface {
// NetworkStats returns statistics concerning the current state of the
// known channel graph within the network.
NetworkStats(ctx context.Context) (*models.NetworkStats, error)
// BetweennessCentrality computes the normalised and non-normalised
// betweenness centrality for each node in the graph.
BetweennessCentrality(ctx context.Context) (
map[autopilot.NodeID]*models.BetweennessCentrality, error)
}

View File

@ -12,7 +12,6 @@ import (
"net/http"
"os"
"path/filepath"
"runtime"
"sort"
"strconv"
"strings"
@ -6701,43 +6700,27 @@ func (r *rpcServer) GetNodeMetrics(ctx context.Context,
return nil, nil
}
resp := &lnrpc.NodeMetricsResponse{
BetweennessCentrality: make(map[string]*lnrpc.FloatMetric),
}
graph := r.server.graphSource
// Obtain the pointer to the global singleton channel graph, this will
// provide a consistent view of the graph due to bolt db's
// transactional model.
graph := r.server.graphDB
// Calculate betweenness centrality if requested. Note that depending on the
// graph size, this may take up to a few minutes.
channelGraph := autopilot.ChannelGraphFromDatabase(graph)
centralityMetric, err := autopilot.NewBetweennessCentralityMetric(
runtime.NumCPU(),
)
// Calculate betweenness centrality if requested. Note that depending on
// the graph size, this may take up to a few minutes.
centrality, err := graph.BetweennessCentrality(ctx)
if err != nil {
return nil, err
}
if err := centralityMetric.Refresh(channelGraph); err != nil {
return nil, err
result := make(map[string]*lnrpc.FloatMetric)
for nodeID, betweenness := range centrality {
id := hex.EncodeToString(nodeID[:])
result[id] = &lnrpc.FloatMetric{
Value: betweenness.NonNormalized,
NormalizedValue: betweenness.Normalized,
}
}
// Fill normalized and non normalized centrality.
centrality := centralityMetric.GetMetric(true)
for nodeID, val := range centrality {
resp.BetweennessCentrality[hex.EncodeToString(nodeID[:])] =
&lnrpc.FloatMetric{
NormalizedValue: val,
}
}
centrality = centralityMetric.GetMetric(false)
for nodeID, val := range centrality {
resp.BetweennessCentrality[hex.EncodeToString(nodeID[:])].Value = val
}
return resp, nil
return &lnrpc.NodeMetricsResponse{
BetweennessCentrality: result,
}, nil
}
// GetChanInfo returns the latest authenticated network announcement for the