diff --git a/graph/db/models/stats.go b/graph/db/models/stats.go index 833770bed..a737fe7d4 100644 --- a/graph/db/models/stats.go +++ b/graph/db/models/stats.go @@ -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 +} diff --git a/graph/sources/chan_graph.go b/graph/sources/chan_graph.go index 5944b0221..99ee0f0eb 100644 --- a/graph/sources/chan_graph.go +++ b/graph/sources/chan_graph.go @@ -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 { diff --git a/graph/sources/interfaces.go b/graph/sources/interfaces.go index 2d483c2ea..448c5bb00 100644 --- a/graph/sources/interfaces.go +++ b/graph/sources/interfaces.go @@ -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) } diff --git a/rpcserver.go b/rpcserver.go index 4c56546e1..e3ea23f85 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -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