mirror of
https://github.com/lightninglabs/faraday.git
synced 2026-08-13 12:33:35 +02:00
insights: add channel insights package
This commit is contained in:
parent
98848af064
commit
b70cd0aa3f
2 changed files with 272 additions and 0 deletions
123
insights/insights.go
Normal file
123
insights/insights.go
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
package insights
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/lightninglabs/terminator/revenue"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
)
|
||||
|
||||
// ChannelInfo provides a set of performance metrics for a lightning channel.
|
||||
type ChannelInfo struct {
|
||||
// ChannelPoint is the outpoint of the channel's funding transaction.
|
||||
ChannelPoint string
|
||||
|
||||
// MonitoredFor is the amount of time the channel's uptime has been
|
||||
// monitored by lnd.
|
||||
MonitoredFor time.Duration
|
||||
|
||||
// Uptime is the total amount of time the channel's remote peer has
|
||||
// been online for.
|
||||
Uptime time.Duration
|
||||
|
||||
// VolumeIncoming is the volume in millisatoshis that the channel has
|
||||
// forwarded through the node as the incoming channel.
|
||||
VolumeIncoming lnwire.MilliSatoshi
|
||||
|
||||
// VolumeOutgoing is the volume in millisatoshis that the channel has
|
||||
// forwarded through the node as the outgoing channel.
|
||||
VolumeOutgoing lnwire.MilliSatoshi
|
||||
|
||||
// FeesEarned is the total fees earned by the channel while routing.
|
||||
// Note that fees are split evenly between incoming and outgoing
|
||||
// channels.
|
||||
FeesEarned lnwire.MilliSatoshi
|
||||
|
||||
// Confirmations is the number of confirmations the funding transction
|
||||
// has.
|
||||
Confirmations uint32
|
||||
|
||||
// Private indicates whether the channel is private.
|
||||
Private bool
|
||||
}
|
||||
|
||||
// Config provides insights with everything it needs to obtain channel
|
||||
// insights.
|
||||
type Config struct {
|
||||
// OpenChannels is a function which returns all of our currently open,
|
||||
// public and private channels.
|
||||
OpenChannels func() ([]*lnrpc.Channel, error)
|
||||
|
||||
// CurrentHeight is a function which returns the current block
|
||||
// currentHeight.
|
||||
CurrentHeight func() (uint32, error)
|
||||
|
||||
// RevenueReport is a report our channels revenue.
|
||||
RevenueReport *revenue.Report
|
||||
}
|
||||
|
||||
// GetChannels returns an array of channel insights.
|
||||
func GetChannels(cfg *Config) ([]*ChannelInfo, error) {
|
||||
// Get the current block height.
|
||||
height, err := cfg.CurrentHeight()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
channels, err := cfg.OpenChannels()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
insights := make([]*ChannelInfo, 0, len(channels))
|
||||
for _, channel := range channels {
|
||||
// Get the short channel ID so we can calculate the number of
|
||||
// blocks the channel has been open for.
|
||||
shortID := lnwire.NewShortChanIDFromInt(channel.ChanId)
|
||||
|
||||
// Calculate the number of confirmations the channel has. We
|
||||
// do not need to check whether channel height >= current
|
||||
// height because we are working with already open channels.
|
||||
// If the funding transaction is in the current block, it is
|
||||
// considered to have one confirmation, so we add one to the
|
||||
// current height to reflect this.
|
||||
confirmations := (height + 1) - shortID.BlockHeight
|
||||
|
||||
monitored := time.Second * time.Duration(channel.Lifetime)
|
||||
uptime := time.Second * time.Duration(channel.Uptime)
|
||||
|
||||
// Create a channel insight for the channel.
|
||||
channelInsight := &ChannelInfo{
|
||||
ChannelPoint: channel.ChannelPoint,
|
||||
MonitoredFor: monitored,
|
||||
Uptime: uptime,
|
||||
Confirmations: confirmations,
|
||||
Private: channel.Private,
|
||||
}
|
||||
|
||||
// If the channel is not present in the revenue report, it has
|
||||
// not generated any revenue over the period so we can add it
|
||||
// to our set of insights and proceed to the next channel.
|
||||
reports, ok := cfg.RevenueReport.ChannelPairs[channel.ChannelPoint]
|
||||
if !ok {
|
||||
insights = append(insights, channelInsight)
|
||||
continue
|
||||
}
|
||||
|
||||
// Accumulate revenue totals for the channel.
|
||||
for _, rev := range reports {
|
||||
channelInsight.VolumeIncoming += rev.AmountIncoming
|
||||
channelInsight.VolumeOutgoing += rev.AmountOutgoing
|
||||
|
||||
// We spilt fees evenly between the channels, so that
|
||||
// we do not double count fees.
|
||||
channelInsight.FeesEarned +=
|
||||
(rev.FeesOutgoing + rev.FeesIncoming) / 2
|
||||
}
|
||||
|
||||
insights = append(insights, channelInsight)
|
||||
}
|
||||
|
||||
return insights, nil
|
||||
}
|
||||
149
insights/insights_test.go
Normal file
149
insights/insights_test.go
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
package insights
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/lightninglabs/terminator/revenue"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
)
|
||||
|
||||
// TestGetChannels tests gathering of channel insights from a set of lnrpc
|
||||
// channels and a revenue report.
|
||||
func TestGetChannels(t *testing.T) {
|
||||
// Make a short channel ID for a channel at height 1000.
|
||||
channelHeight1000 := lnwire.ShortChannelID{
|
||||
BlockHeight: 1000,
|
||||
TxIndex: 1,
|
||||
TxPosition: 3,
|
||||
}
|
||||
|
||||
// Create an empty revenue report.
|
||||
noRevenue := &revenue.Report{
|
||||
ChannelPairs: map[string]map[string]revenue.Revenue{},
|
||||
}
|
||||
|
||||
// report is a revenue report with the channel opened in block 1000 in
|
||||
// it.
|
||||
report := &revenue.Report{
|
||||
ChannelPairs: map[string]map[string]revenue.Revenue{
|
||||
"a:1": {
|
||||
"b:1": revenue.Revenue{
|
||||
AmountOutgoing: 25,
|
||||
AmountIncoming: 10,
|
||||
FeesIncoming: 10,
|
||||
FeesOutgoing: 10,
|
||||
},
|
||||
"b:2": revenue.Revenue{
|
||||
AmountOutgoing: 0,
|
||||
AmountIncoming: 10,
|
||||
FeesIncoming: 20,
|
||||
FeesOutgoing: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
hourInSeconds := int64(time.Hour.Seconds())
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
channels []*lnrpc.Channel
|
||||
currentHeight uint32
|
||||
revenue *revenue.Report
|
||||
expectedInsights []*ChannelInfo
|
||||
}{
|
||||
{
|
||||
name: "no channels",
|
||||
channels: []*lnrpc.Channel{},
|
||||
currentHeight: 2000,
|
||||
revenue: noRevenue,
|
||||
expectedInsights: []*ChannelInfo{},
|
||||
}, {
|
||||
name: "one confirmation",
|
||||
channels: []*lnrpc.Channel{
|
||||
{
|
||||
ChannelPoint: "a:1",
|
||||
Lifetime: hourInSeconds,
|
||||
Uptime: hourInSeconds / 2,
|
||||
ChanId: channelHeight1000.ToUint64(),
|
||||
},
|
||||
},
|
||||
currentHeight: 1000,
|
||||
revenue: noRevenue,
|
||||
expectedInsights: []*ChannelInfo{
|
||||
{
|
||||
ChannelPoint: "a:1",
|
||||
MonitoredFor: time.Hour,
|
||||
Uptime: time.Minute * 30,
|
||||
Confirmations: 1,
|
||||
Private: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "two confirmations",
|
||||
channels: []*lnrpc.Channel{
|
||||
{
|
||||
ChannelPoint: "a:1",
|
||||
Lifetime: hourInSeconds,
|
||||
Uptime: hourInSeconds / 2,
|
||||
ChanId: channelHeight1000.ToUint64(),
|
||||
},
|
||||
},
|
||||
currentHeight: 1001,
|
||||
revenue: report,
|
||||
expectedInsights: []*ChannelInfo{
|
||||
{
|
||||
ChannelPoint: "a:1",
|
||||
MonitoredFor: time.Hour,
|
||||
Uptime: time.Minute * 30,
|
||||
Confirmations: 2,
|
||||
VolumeIncoming: 20,
|
||||
VolumeOutgoing: 25,
|
||||
FeesEarned: 20,
|
||||
Private: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
insights, err := GetChannels(&Config{
|
||||
OpenChannels: func() (
|
||||
channels []*lnrpc.Channel, err error) {
|
||||
|
||||
return test.channels, nil
|
||||
},
|
||||
CurrentHeight: func() (u uint32, e error) {
|
||||
return test.currentHeight, nil
|
||||
},
|
||||
RevenueReport: test.revenue,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if len(test.expectedInsights) != len(insights) {
|
||||
t.Fatalf("expected: %v insights, got: %v",
|
||||
len(test.expectedInsights),
|
||||
len(insights))
|
||||
}
|
||||
|
||||
for i, insight := range test.expectedInsights {
|
||||
if !reflect.DeepEqual(insights[i], insight) {
|
||||
t.Fatalf("expected: %v, got: %v",
|
||||
insight, insights[i])
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue