Merge pull request #9 from lightninglabs/recs-revenuebased

trmrpc: Add revenue and volume based close recommendations
This commit is contained in:
Carla Kirk-Cohen 2020-03-04 05:01:22 -03:00 committed by GitHub
commit f9d7d86315
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 1224 additions and 424 deletions

View file

@ -15,6 +15,17 @@ var channelInsightsCommand = cli.Command{
Action: queryChannelInsights,
}
// insightsResp is used to display additional information that is calculated
// from the channel insight in the cli response.
type insightsResp struct {
*trmrpc.ChannelInsight
UptimeRatio float64 `json:"uptime_ratio"`
RevenuePerConfirmation float64 `json:"revenue_per_conf_msat"`
VolumePerConfirmation float64 `json:"volume_per_conf_msat"`
IncomingVolumePerConfirmation float64 `json:"incoming_vol_per_conf_msat"`
OutgoingVolumePerConfirmation float64 `json:"outgoing_vol_per_conf_msat"`
}
func queryChannelInsights(ctx *cli.Context) error {
client, cleanup := getClient(ctx)
defer cleanup()
@ -27,17 +38,14 @@ func queryChannelInsights(ctx *cli.Context) error {
return err
}
type insightsResp struct {
*trmrpc.ChannelInsight
UptimeRatio float64 `json:"uptime_ratio"`
RevenuePerConfirmation float64 `json:"revenue_per_confirmation_msat"`
}
insights := make([]insightsResp, len(resp.ChannelInsights))
for i, channel := range resp.ChannelInsights {
confirmations := float64(channel.Confirmations)
insight := insightsResp{
ChannelInsight: channel,
RevenuePerConfirmation: float64(channel.FeesEarnedMsat) /
float64(channel.Confirmations),
confirmations,
}
if channel.MonitoredSeconds != 0 {
@ -45,6 +53,20 @@ func queryChannelInsights(ctx *cli.Context) error {
float64(channel.MonitoredSeconds)
}
// Calculate incoming, outgoing and total volume per
// confirmation.
insight.IncomingVolumePerConfirmation =
float64(channel.VolumeIncomingMsat) /
confirmations
insight.OutgoingVolumePerConfirmation =
float64(channel.VolumeOutgoingMsat) /
confirmations
insight.VolumePerConfirmation =
insight.IncomingVolumePerConfirmation +
confirmations
insights[i] = insight
}

View file

@ -2,6 +2,7 @@ package main
import (
"context"
"fmt"
"time"
"github.com/lightninglabs/terminator/trmrpc"
@ -11,19 +12,51 @@ import (
var (
defaultMinMonitored = time.Hour * 24 * 7 * 4 // four weeks in hours
defaultOutlierMultiplier = 3
)
var closeRecommendationCommand = cli.Command{
Name: "closerecs",
Category: "channel",
Usage: "Get close recommendations for currently open channels.",
Flags: []cli.Flag{
cli.Int64Flag{
Name: "min_monitored",
Usage: "amount of time in seconds a channel should " +
"be monitored for to be eligible for close",
Value: int64(defaultMinMonitored.Seconds()),
// monitoredFlag is common to recommendation requests.
monitoredFlag = cli.Int64Flag{
Name: "min_monitored",
Usage: "amount of time in seconds a channel should be monitored " +
"for to be eligible for close",
Value: int64(defaultMinMonitored.Seconds()),
}
// Flags required for threshold close recommendations.
thresholdFlags = []cli.Flag{
cli.Float64Flag{
Name: "uptime",
Usage: "Ratio of uptime to time monitored, expressed" +
"in [0;1].",
},
cli.Float64Flag{
Name: "revenue",
Usage: "threshold revenue (in msat) per confirmation " +
"beneath which channels will be identified " +
"for close.",
},
cli.Float64Flag{
Name: "incoming",
Usage: "threshold incoming volume (in msat) per " +
"confirmation beneath which channels will be " +
"identified for close",
},
cli.Float64Flag{
Name: "outgoing",
Usage: "threshold outgoing volume (in msat) per " +
"confirmation beneath which channels will be " +
"identified for close",
},
cli.Float64Flag{
Name: "volume",
Usage: "threshold total volume (in msat) per " +
"confirmation beneath which channels will be " +
"identified for close",
},
monitoredFlag,
}
// Flags required for outlier close recommendations.
outlierFlags = []cli.Flag{
cli.StringFlag{
Name: "outlier_mult",
Usage: "(optional with outlier strategy) Number of " +
@ -32,43 +65,147 @@ var closeRecommendationCommand = cli.Command{
"Recommended values are 1.5 for aggressive " +
"recommendations and 3 for conservative ones.",
},
cli.StringFlag{
Name: "uptime_threshold",
Usage: "(optional) Uptime percentage threshold " +
"underneath which a channel will be recommended " +
"for close.",
cli.BoolFlag{
Name: "uptime",
Usage: "set to get recommendations based on the " +
"channel's peer's ratio of uptime to time " +
"monitored",
},
},
Action: queryCloseRecommendations,
cli.BoolFlag{
Name: "revenue",
Usage: "get recommendations based on the " +
"channel's revenue per confirmation",
},
cli.BoolFlag{
Name: "incoming_volume",
Usage: "get recommendations based on the " +
"channel's incoming volume per confirmation",
},
cli.BoolFlag{
Name: "outgoing_volume",
Usage: "get recommendations based on the " +
"channel's outgoing volume per confirmation",
},
cli.BoolFlag{
Name: "volume",
Usage: "get recommendations based on the " +
"channel's total volume per confirmation",
},
monitoredFlag,
}
)
var thresholdRecommendationCommand = cli.Command{
Name: "threshold",
Category: "recommendations",
Usage: "Get close recommendations for currently open channels " +
"based whether they are below a set threshold.",
Flags: thresholdFlags,
Action: queryThresholdRecommendations,
}
func queryCloseRecommendations(ctx *cli.Context) error {
func queryThresholdRecommendations(ctx *cli.Context) error {
client, cleanup := getClient(ctx)
defer cleanup()
// Set monitored value from cli and default outlier multiplier. The
// outlier multiplier will be overwritten if the user provided it.
req := &trmrpc.CloseRecommendationsRequest{
MinimumMonitored: ctx.Int64("min_monitored"),
OutlierMultiplier: float32(defaultOutlierMultiplier),
// Set monitored value from cli values, this value will always be
// non-zero because the flag has a default.
req := &trmrpc.ThresholdRecommendationsRequest{
RecRequest: &trmrpc.CloseRecommendationRequest{
MinimumMonitored: ctx.Int64("min_monitored"),
},
}
// If an a custom outlier multiple was set, use it.
if ctx.IsSet("outlier_mult") {
req.OutlierMultiplier = float32(ctx.Float64("outlier_mult"))
}
// Set threshold and metric based on uptime/revenue flags.
switch {
case ctx.IsSet("uptime"):
req.ThresholdValue = float32(ctx.Float64("uptime"))
req.RecRequest.Metric = trmrpc.CloseRecommendationRequest_UPTIME
// If an uptime threshold was set, use it.
if ctx.IsSet("uptime_threshold") {
uptimeThreshold := float32(ctx.Float64("uptime_threshold"))
req.Threshold =
&trmrpc.CloseRecommendationsRequest_UptimeThreshold{
UptimeThreshold: uptimeThreshold,
}
case ctx.IsSet("revenue"):
req.ThresholdValue = float32(ctx.Float64("revenue"))
req.RecRequest.Metric = trmrpc.CloseRecommendationRequest_REVENUE
case ctx.IsSet("incoming"):
req.ThresholdValue = float32(ctx.Float64("incoming"))
req.RecRequest.Metric = trmrpc.CloseRecommendationRequest_INCOMING_VOLUME
case ctx.IsSet("outgoing"):
req.ThresholdValue = float32(ctx.Float64("outgoing"))
req.RecRequest.Metric = trmrpc.CloseRecommendationRequest_OUTGOING_VOLUME
case ctx.IsSet("volume"):
req.ThresholdValue = float32(ctx.Float64("volume"))
req.RecRequest.Metric = trmrpc.CloseRecommendationRequest_TOTAL_VOLUME
default:
return fmt.Errorf("threshold required")
}
rpcCtx := context.Background()
recs, err := client.CloseRecommendations(rpcCtx, req)
recs, err := client.ThresholdRecommendations(rpcCtx, req)
if err != nil {
return err
}
printRespJSON(recs)
return nil
}
var outlierRecommendationCommand = cli.Command{
Name: "outliers",
Category: "recommendations",
Usage: "Get close recommendations for currently open channels " +
"based on whether it is an outlier.",
Flags: outlierFlags,
Action: queryOutlierRecommendations,
}
func queryOutlierRecommendations(ctx *cli.Context) error {
client, cleanup := getClient(ctx)
defer cleanup()
// Set monitored value from cli and default outlier multiplier. The
// outlier multiplier will be overwritten if the user provided it, and
// the monitored value will always be non-zero because the flag has a
// default value.
req := &trmrpc.OutlierRecommendationsRequest{
RecRequest: &trmrpc.CloseRecommendationRequest{
MinimumMonitored: ctx.Int64("min_monitored"),
},
OutlierMultiplier: float32(defaultOutlierMultiplier),
}
// If an a custom outlier multiple was set, use it.
if ctx.IsSet("outlier_mult") {
req.OutlierMultiplier = float32(ctx.Float64("outlier_mult"))
}
// Set metric based on uptime or revenue flags.
switch {
case ctx.IsSet("uptime"):
req.RecRequest.Metric = trmrpc.CloseRecommendationRequest_UPTIME
case ctx.IsSet("revenue"):
req.RecRequest.Metric = trmrpc.CloseRecommendationRequest_REVENUE
case ctx.IsSet("incoming_volume"):
req.RecRequest.Metric = trmrpc.CloseRecommendationRequest_INCOMING_VOLUME
case ctx.IsSet("outgoing_volume"):
req.RecRequest.Metric = trmrpc.CloseRecommendationRequest_OUTGOING_VOLUME
case ctx.IsSet("volume"):
req.RecRequest.Metric = trmrpc.CloseRecommendationRequest_TOTAL_VOLUME
default:
return fmt.Errorf("uptime, revenue or volume realted flag " +
"required")
}
rpcCtx := context.Background()
recs, err := client.OutlierRecommendations(rpcCtx, req)
if err != nil {
return err
}

View file

@ -23,7 +23,8 @@ func main() {
},
}
app.Commands = []cli.Command{
closeRecommendationCommand,
thresholdRecommendationCommand,
outlierRecommendationCommand,
revenueReportCommand,
channelInsightsCommand,
}

View file

@ -4,7 +4,11 @@
// time will be considered for closing.
//
// Channels will be assessed based on the following data points:
// - Uptime percentage
// - Uptime ratio
// - Fee revenue per block capital has been committed for
// - Incoming volume per block capital has been committed for
// - Outgoing volume per block capital has been committed for
// - Total volume per block capital has been committed for
//
// Channels that are outliers within the set of channels that are eligible for
// close recommendation will be recommended for closure.
@ -15,7 +19,7 @@ import (
"time"
"github.com/lightninglabs/terminator/dataset"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightninglabs/terminator/insights"
)
var (
@ -24,30 +28,56 @@ var (
errZeroMinMonitored = errors.New("must provide a non-zero minimum " +
"monitor time for channel exclusion")
// ErrNoMetric is returned when a close recommendations with no chosen
// metric is provided.
ErrNoMetric = errors.New("metric required for close " +
"recommendations")
// DefaultOutlierMultiplier is the default value used in close
// recommendations based on outliers when there is no user provided
// value.
DefaultOutlierMultiplier float64 = 3
)
// Metric is an enum which indicate what data point our recommendations should
// be based on.
type Metric int
const (
invalidMetric Metric = iota
// UptimeMetric bases recommendations on the uptime of the channel's
// remote peer.
UptimeMetric
// RevenueMetric bases recommendations on the revenue that the channel
// has generated per block that our capital has been committed for.
RevenueMetric
// IncomingVolume bases recommendations on the incoming volume that the
// channel has processed, scaled by funding transaction confirmations.
IncomingVolume
// IncomingVolume bases recommendations on the incoming volume that the
// channel has processed, scaled by funding transaction confirmations.
OutgoingVolume
// Volume bases recommendations on the total volume that the
// channel has processed, scaled by funding transaction confirmations.
Volume
)
// CloseRecommendationConfig provides the functions and parameters required to
// provide close recommendations.
// provide close recommendations. This struct holds fields which are common to
// all recommendation calculation strategies.
type CloseRecommendationConfig struct {
// OpenChannels is a function which returns all of our currently open,
// public channels.
OpenChannels func() ([]*lnrpc.Channel, error)
// ChannelInsights is a function which returns a set of channel insights
// for our current set of channels.
ChannelInsights func() ([]*insights.ChannelInfo, error)
// OutlierMultiplier is the number of inter quartile ranges a value
// should be away from the lower/upper quartile to be considered an
// outlier. Recommended values are 1.5 for more aggressive
// recommendations and 3 for more cautious recommendations.
OutlierMultiplier float64
// UptimeThreshold is the uptime percentage over the channel's observed
// lifetime beneath which channels will be recommended for close. This
// value is expressed as a percentage in [0,1], and will default to 0 if
// it is not set.
UptimeThreshold float64
// Metric defines the metric that we will use to provide close
// recommendations. Calls will fail if no value is provided.
Metric Metric
// MinimumMonitored is the minimum amount of time that a channel must
// have been monitored for before it is considered for closing.
@ -72,29 +102,53 @@ type Report struct {
// for long enough to be considered for close.
ConsideredChannels int
// OutlierRecommendations is a map of chanel outpoints to a bool which
// indicates whether we should close the channel based on whether it is
// an outlier.
OutlierRecommendations map[string]Recommendation
// ThresholdRecommendations is a map of chanel outpoints to a bool which
// indicates whether we should close the channel based on whether it is
// below a user provided threshold.
ThresholdRecommendations map[string]Recommendation
// Recommendations is a map of chanel outpoints to a bool which
// indicates whether we should close the channel.
Recommendations map[string]Recommendation
}
// CloseRecommendations returns a report which contains information about the
// channels that were considered and a list of close recommendations. Channels
// are considered for close if their uptime percentage is a lower outlier in
// uptime percentage dataset.
func CloseRecommendations(cfg *CloseRecommendationConfig) (*Report, error) {
// OutlierRecommendations returns recommendations based on whether a value is a
// lower outlier within its current dataset. It takes an outlier multiplier value
// which is the number of inter quartile ranges a value should be away from the
// lower/upper quartile to be considered an outlier. Recommended values are 1.5
// for more aggressive recommendations and 3 for more cautious recommendations.
func OutlierRecommendations(cfg *CloseRecommendationConfig,
outlierMultiplier float64) (*Report, error) {
getRecs := func(dataset dataset.Dataset) (map[string]Recommendation, error) {
return getOutlierRecs(dataset, outlierMultiplier, false)
}
return closeRecommendations(cfg, getRecs)
}
// ThresholdRecommendations returns a recommendations based on whether a value is
// below a given threshold.
func ThresholdRecommendations(cfg *CloseRecommendationConfig,
threshold float64) (*Report, error) {
getRecs := func(dataset dataset.Dataset) (map[string]Recommendation, error) {
return getThresholdRecs(dataset, threshold, true), nil
}
return closeRecommendations(cfg, getRecs)
}
// closeRecommendations returns a report which contains information about the
// channels that were considered and a list of close recommendations. It takes
// a function which can produce the relevant dataset from a set of channel
// insights and a function which can produces recommendations as parameters.
func closeRecommendations(cfg *CloseRecommendationConfig,
getRecommendations func(data dataset.Dataset) (
map[string]Recommendation, error)) (*Report, error) {
// Check that the minimum wait time is non-zero.
if cfg.MinimumMonitored == 0 {
return nil, errZeroMinMonitored
}
// Get the set of currently open channels.
channels, err := cfg.OpenChannels()
// Get the set of insights for our currently open channels.
channels, err := cfg.ChannelInsights()
if err != nil {
return nil, err
}
@ -102,48 +156,62 @@ func CloseRecommendations(cfg *CloseRecommendationConfig) (*Report, error) {
// Filter out channels that are below the minimum required age.
filtered := filterChannels(channels, cfg.MinimumMonitored)
// Produce a dataset containing uptime percentage for channels that have
// been monitored for longer than the minimum time.
uptime := getUptimeDataset(filtered)
report := &Report{
TotalChannels: len(channels),
ConsideredChannels: len(uptime),
ConsideredChannels: len(filtered),
}
var data dataset.Dataset
switch cfg.Metric {
case UptimeMetric:
data = getUptimeDataset(filtered)
case RevenueMetric:
data = getConfirmationScaledDataset(revenueValue, filtered)
case IncomingVolume:
data = getConfirmationScaledDataset(
incomingVolumeValue, filtered,
)
case OutgoingVolume:
data = getConfirmationScaledDataset(
outgoingVolumeValue, filtered,
)
case Volume:
data = getConfirmationScaledDataset(totalVolumeValue, filtered)
default:
return nil, ErrNoMetric
}
// Get close recommendations based on outliers.
report.OutlierRecommendations, err = getOutlierRecs(
uptime, cfg.OutlierMultiplier,
)
report.Recommendations, err = getRecommendations(data)
if err != nil {
return nil, err
}
// Get close recommendations based on threshold.
report.ThresholdRecommendations = getThresholdRecs(
uptime, cfg.UptimeThreshold,
)
return report, nil
}
// getThresholdRecs returns a map of channel points to values that are below a
// given threshold.
func getThresholdRecs(uptime dataset.Dataset,
threshold float64) map[string]Recommendation {
// getThresholdRecs returns a map of channel points to values that are above
// or below a given threshold.
func getThresholdRecs(values dataset.Dataset,
threshold float64, belowThreshold bool) map[string]Recommendation {
// Get a map of channel labels to a boolean indicating whether
// they are beneath the threshold.
thresholdValues := uptime.GetThreshold(threshold, true)
thresholdValues := values.GetThreshold(threshold, belowThreshold)
recommendations := make(
map[string]Recommendation, len(thresholdValues),
)
for chanPoint, belowThrehsold := range thresholdValues {
for chanPoint, crossesThreshold := range thresholdValues {
recommendations[chanPoint] = Recommendation{
Value: uptime.Value(chanPoint),
RecommendClose: belowThrehsold,
Value: values.Value(chanPoint),
RecommendClose: crossesThreshold,
}
}
@ -151,75 +219,150 @@ func getThresholdRecs(uptime dataset.Dataset,
}
// getOutlierRecs generates map of channel outpoint strings to booleans
// indicating whether we recommend closing a channel.
func getOutlierRecs(uptime dataset.Dataset,
outlierMultiplier float64) (map[string]Recommendation, error) {
// indicating whether we recommend closing a channel. It takes a outlier
// multiplier which scales the degree to which we want to calculate outliers,
// and an upper outlier boolean which determines whether we want to identify
// upper or lower outliers.
func getOutlierRecs(values dataset.Dataset,
outlierMultiplier float64,
upperOutlier bool) (map[string]Recommendation, error) {
recommendations := make(map[string]Recommendation)
outliers, err := uptime.GetOutliers(outlierMultiplier)
outliers, err := values.GetOutliers(outlierMultiplier)
if err != nil {
return nil, err
}
// Add a recommendation for each channel to our set of recommendations.
// If the channel is a lower outlier, we recommend it for close.
// RecommendClose in the recommendation will be set to true if the
// channel matches the outlier type we are looking for (upper or
// lower).
for chanPoint, outlier := range outliers {
var recommendClose bool
// If we want to detect upper outliers, and the channel is a
// upper outlier, set recommend close to true.
if upperOutlier && outlier.UpperOutlier {
recommendClose = true
}
// If we want to detect lower outliers, and the channel is a
// lower outlier, set recommend close to true.
if !upperOutlier && outlier.LowerOutlier {
recommendClose = true
}
recommendations[chanPoint] = Recommendation{
Value: uptime.Value(chanPoint),
RecommendClose: outlier.LowerOutlier,
Value: values.Value(chanPoint),
RecommendClose: recommendClose,
}
}
return recommendations, nil
}
// filterChannels filters out channels that are beneath the minimum age and
// produces a map of channel outpoint strings to rpc channels which contains
// the channels that are eligible for close recommendation.
func filterChannels(openChannels []*lnrpc.Channel,
minimumAge time.Duration) map[string]*lnrpc.Channel {
// filterChannels filters out channels that are beneath the minimum age, or
// are private and returns a set of channels that are eligible for close
// recommendations.
func filterChannels(channelInsights []*insights.ChannelInfo,
minimumAge time.Duration) []*insights.ChannelInfo {
// Create a map which will hold channel point labels to uptime
// percentage.
channels := make(map[string]*lnrpc.Channel)
filteredChannels := make(
[]*insights.ChannelInfo, 0, len(channelInsights),
)
for _, channel := range channelInsights {
if channel.MonitoredFor < minimumAge {
log.Tracef("Channel: %v has not been "+
"monitored for long enough, excluding it "+
"from consideration", channel.ChannelPoint)
for _, channel := range openChannels {
if channel.Lifetime < int64(minimumAge.Seconds()) {
log.Tracef("Channel: %v has not been monitored for "+
"long enough, excluding it from consideration",
channel.ChannelPoint)
continue
}
channels[channel.ChannelPoint] = channel
if channel.Private {
log.Tracef("Channel: %v is private, excluding "+
"it from consideration", channel.ChannelPoint)
continue
}
filteredChannels = append(filteredChannels, channel)
}
log.Debugf("considering: %v channels for close out of %v",
len(channels), len(openChannels))
len(filteredChannels), len(channelInsights))
return channels
return filteredChannels
}
// getUptimeDataset takes a set of channels that are eligible for close and
// produces an uptime dataset.
func getUptimeDataset(
eligibleChannels map[string]*lnrpc.Channel) dataset.Dataset {
eligibleChannels []*insights.ChannelInfo) dataset.Dataset {
// Create a map which will hold channel point string label to uptime
// percentage.
var channels = make(map[string]float64)
// ratio.
var channels = make(map[string]float64, len(eligibleChannels))
for outpoint, channel := range eligibleChannels {
// Calculate the uptime percentage for the channel and add it
for _, channel := range eligibleChannels {
// Calculate the uptime ratio for the channel and add it
// to the channel -> uptime map.
uptimePercentage := float64(channel.Uptime) / float64(channel.Lifetime)
channels[outpoint] = uptimePercentage
uptimeRatio := float64(channel.Uptime) /
float64(channel.MonitoredFor)
log.Tracef("channel: %v has uptime percentage: %v",
outpoint, uptimePercentage)
channels[channel.ChannelPoint] = uptimeRatio
}
// Create a dataset for the uptime values we have collected.
return dataset.New(channels)
}
// getConfirmationScaledDataset returns a dataset that scales a value by the
// number of confirmations its funding transaction has. It takes a function
// which gets the relevant value from the channel insight as input.
func getConfirmationScaledDataset(getValue perConfirmationValue,
eligibleChannels []*insights.ChannelInfo) dataset.Dataset {
// Create a map which will hold channel point string label to revenue
// per block that we have had revenue committed for.
var channels = make(map[string]float64, len(eligibleChannels))
for _, channel := range eligibleChannels {
// Channels cannot have zero confirmations because we are
// dealing with open (ie confirmed) channels, so we can
// get the value and scale it by our confirmation total.
valuePerConfirmation :=
getValue(channel) /
float64(channel.Confirmations)
channels[channel.ChannelPoint] = valuePerConfirmation
}
return channels
}
// perConfirmationValue is a function which gets a value from a channel insight
// that needs to be scaled by its number of confirmations.
type perConfirmationValue func(channel *insights.ChannelInfo) float64
// revenueValue gets total revenue for a channel.
func revenueValue(channel *insights.ChannelInfo) float64 {
return float64(channel.FeesEarned)
}
// incomingVolumeValue gets total incoming volume for a channel.
func incomingVolumeValue(channel *insights.ChannelInfo) float64 {
return float64(channel.VolumeIncoming)
}
// outgoingVolumeValue gets total outgoing volume for a channel.
func outgoingVolumeValue(channel *insights.ChannelInfo) float64 {
return float64(channel.VolumeOutgoing)
}
// totalVolumeValue gets total volume for a channel.
func totalVolumeValue(channel *insights.ChannelInfo) float64 {
return float64(channel.VolumeIncoming + channel.VolumeOutgoing)
}

View file

@ -6,7 +6,7 @@ import (
"time"
"github.com/lightninglabs/terminator/dataset"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightninglabs/terminator/insights"
)
// TestCloseRecommendations tests CloseRecommendations for error cases where
@ -15,56 +15,72 @@ import (
// the minimum acceptable number of channels. It does not test the report
// provided, because that will be covered by further tests.
func TestCloseRecommendations(t *testing.T) {
var (
openChanErr = errors.New("intentional test err")
hourSeconds = int64(time.Hour.Seconds())
)
var openChanErr = errors.New("intentional test err")
tests := []struct {
name string
OpenChannels func() ([]*lnrpc.Channel, error)
upperOutlier bool
metric Metric
ChanInsights func() ([]*insights.ChannelInfo, error)
MinMonitored time.Duration
expectedErr error
}{
{
name: "no channels",
OpenChannels: func() ([]*lnrpc.Channel, error) {
name: "no channels",
upperOutlier: false,
metric: UptimeMetric,
ChanInsights: func() ([]*insights.ChannelInfo, error) {
return nil, nil
},
MinMonitored: time.Hour,
expectedErr: nil,
},
{
name: "open channels fails",
OpenChannels: func() ([]*lnrpc.Channel, error) {
name: "channel insights fails",
upperOutlier: false,
metric: invalidMetric,
ChanInsights: func() ([]*insights.ChannelInfo, error) {
return nil, nil
},
MinMonitored: time.Hour,
expectedErr: ErrNoMetric,
},
{
name: "channel insights fails",
metric: UptimeMetric,
ChanInsights: func() ([]*insights.ChannelInfo, error) {
return nil, openChanErr
},
MinMonitored: time.Hour,
expectedErr: openChanErr,
},
{
name: "zero min monitored",
OpenChannels: func() ([]*lnrpc.Channel, error) {
name: "zero min monitored",
upperOutlier: false,
metric: UptimeMetric,
ChanInsights: func() ([]*insights.ChannelInfo, error) {
return nil, nil
},
MinMonitored: 0,
expectedErr: errZeroMinMonitored,
},
{
name: "enough channels",
OpenChannels: func() ([]*lnrpc.Channel, error) {
return []*lnrpc.Channel{
name: "enough channels",
upperOutlier: false,
metric: UptimeMetric,
ChanInsights: func() ([]*insights.ChannelInfo, error) {
return []*insights.ChannelInfo{
{
ChannelPoint: "a:1",
Lifetime: hourSeconds,
MonitoredFor: time.Hour,
},
{
ChannelPoint: "b:2",
Lifetime: hourSeconds,
MonitoredFor: time.Hour,
},
{
ChannelPoint: "c:3",
Lifetime: hourSeconds,
MonitoredFor: time.Hour,
},
}, nil
},
@ -79,13 +95,24 @@ func TestCloseRecommendations(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
_, err := CloseRecommendations(
recFunc := func(data dataset.Dataset) (
m map[string]Recommendation, err error) {
return getOutlierRecs(
data, DefaultOutlierMultiplier,
test.upperOutlier,
)
}
_, err := closeRecommendations(
&CloseRecommendationConfig{
OpenChannels: test.OpenChannels,
OutlierMultiplier: 3,
MinimumMonitored: test.MinMonitored,
ChannelInsights: test.ChanInsights,
MinimumMonitored: test.MinMonitored,
Metric: test.metric,
},
recFunc,
)
if err != test.expectedErr {
t.Fatalf("expected: %v, got: %v",
test.expectedErr, err)
@ -94,19 +121,21 @@ func TestCloseRecommendations(t *testing.T) {
}
}
// TestGetCloseRecs tests the generating of close recommendations for a set of
// channels. It also contains a test case for when there are too few channels
// to calculate outliers, to test that the error is silenced and no
// recommendations are provided.
func TestGetCloseRecs(t *testing.T) {
// TestOutlierRecommendations tests the generating of close recommendations
// for a set of channels based on whether they are outliers. It also contains
// a test case for when there are too few channels to calculate outliers, to
// test that the error is silenced and no recommendations are provided.
func TestOutlierRecommendations(t *testing.T) {
tests := []struct {
name string
upperOutlier bool
channelUptimes map[string]float64
expectedRecs map[string]Recommendation
outlierMultiplier float64
}{
{
name: "not enough values, all false",
name: "not enough values, all false",
upperOutlier: false,
channelUptimes: map[string]float64{
"a:0": 0.7,
},
@ -119,7 +148,9 @@ func TestGetCloseRecs(t *testing.T) {
outlierMultiplier: 2,
},
{
name: "similar values, weak outlier no recommendations",
name: "similar values, weak outlier no " +
"recommendations",
upperOutlier: false,
channelUptimes: map[string]float64{
"a:0": 0.7,
"a:1": 0.6,
@ -133,7 +164,9 @@ func TestGetCloseRecs(t *testing.T) {
},
},
{
name: "similar values, strong outlier no recommendations",
name: "similar values, strong outlier no " +
"make linrecommendations",
upperOutlier: false,
channelUptimes: map[string]float64{
"a:0": 0.7,
"a:1": 0.6,
@ -147,7 +180,8 @@ func TestGetCloseRecs(t *testing.T) {
},
},
{
name: "lower outlier recommended for close",
name: "lower outlier recommended for close",
upperOutlier: false,
channelUptimes: map[string]float64{
"a:0": 0.6,
"a:1": 0.6,
@ -166,7 +200,27 @@ func TestGetCloseRecs(t *testing.T) {
"a:5": {Value: 0.1, RecommendClose: true},
},
},
{
name: "upper outlier recommended for close",
upperOutlier: true,
channelUptimes: map[string]float64{
"a:0": 0.9,
"a:1": 0.2,
"a:2": 0.2,
"a:3": 0.2,
"a:4": 0.1,
"a:5": 0.1,
},
outlierMultiplier: 3,
expectedRecs: map[string]Recommendation{
"a:0": {Value: 0.9, RecommendClose: true},
"a:1": {Value: 0.2, RecommendClose: false},
"a:2": {Value: 0.2, RecommendClose: false},
"a:3": {Value: 0.2, RecommendClose: false},
"a:4": {Value: 0.1, RecommendClose: false},
"a:5": {Value: 0.1, RecommendClose: false},
},
},
{
name: "zero multiplier replaced with default",
channelUptimes: map[string]float64{
@ -199,6 +253,7 @@ func TestGetCloseRecs(t *testing.T) {
recs, err := getOutlierRecs(
uptimeData, test.outlierMultiplier,
test.upperOutlier,
)
if err != nil {
t.Fatalf("unexpected error: %v", err)
@ -225,48 +280,54 @@ func TestGetCloseRecs(t *testing.T) {
}
}
// TestFilterChannels tests filtering of channels based on their lifetime.
func TestFilterChannels(t *testing.T) {
openChannels := []*lnrpc.Channel{
{
ChannelPoint: "a:0",
Lifetime: 10,
Uptime: 1,
},
{
ChannelPoint: "a:1",
Lifetime: 100,
Uptime: 1,
},
{
ChannelPoint: "a:2",
Lifetime: 100,
Uptime: 1,
},
{
ChannelPoint: "a:3",
Lifetime: 100,
Uptime: 1,
},
}
// TestThresholdRecommendations tests getting of recommendations above and
// below a threshold.
func TestThresholdRecommendations(t *testing.T) {
tests := []struct {
name string
openChannels []*lnrpc.Channel
minAge time.Duration
expectedChanPoints []string
name string
belowThreshold bool
threshold float64
values map[string]float64
expectedRecs map[string]Recommendation
}{
{
name: "one filtered - monitored time",
openChannels: openChannels,
minAge: time.Second * 15,
expectedChanPoints: []string{"a:1", "a:2", "a:3"},
name: "nothing below threshold",
belowThreshold: true,
threshold: 0.4,
values: map[string]float64{
"a:0": 0.8,
"a:1": 0.6,
},
expectedRecs: map[string]Recommendation{
"a:0": {Value: 0.8, RecommendClose: false},
"a:1": {Value: 0.6, RecommendClose: false},
},
},
{
name: "all channels included",
openChannels: openChannels,
minAge: time.Second * 5,
expectedChanPoints: []string{"a:0", "a:1", "a:2", "a:3"},
name: "one below threshold",
belowThreshold: true,
threshold: 0.7,
values: map[string]float64{
"a:0": 0.8,
"a:1": 0.6,
},
expectedRecs: map[string]Recommendation{
"a:0": {Value: 0.8, RecommendClose: false},
"a:1": {Value: 0.6, RecommendClose: true},
},
},
{
name: "one above threshold",
belowThreshold: false,
threshold: 0.7,
values: map[string]float64{
"a:0": 0.8,
"a:1": 0.6,
},
expectedRecs: map[string]Recommendation{
"a:0": {Value: 0.8, RecommendClose: true},
"a:1": {Value: 0.6, RecommendClose: false},
},
},
}
@ -276,18 +337,209 @@ func TestFilterChannels(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
filtered := filterChannels(test.openChannels, test.minAge)
recs := getThresholdRecs(
dataset.New(test.values), test.threshold,
test.belowThreshold,
)
if len(test.expectedChanPoints) != len(filtered) {
t.Fatalf("expected: %v channels, got: %v",
len(test.expectedChanPoints),
len(filtered))
if len(test.expectedRecs) != len(recs) {
t.Fatalf("expected: %v recommendations, "+
"got: %v", len(test.expectedRecs),
len(recs))
}
for _, expected := range test.expectedChanPoints {
if _, ok := filtered[expected]; !ok {
t.Fatalf("expected channel: %v to "+
"be present", expected)
// Run through our expected set of true recommendations
// and check that they match the set returned in the
// report.
for channel, expectClose := range test.expectedRecs {
recClose := recs[channel]
if recClose != expectClose {
t.Fatalf("expected close rec: %v"+
" for channel: %v, got: %v",
expectClose, channel, recClose)
}
}
})
}
}
// TestFilterChannels tests filtering of channels based on their lifetime.
func TestFilterChannels(t *testing.T) {
chanInsights := []*insights.ChannelInfo{
{
ChannelPoint: "a:0",
MonitoredFor: 10,
Uptime: 1,
},
{
ChannelPoint: "a:1",
MonitoredFor: 100,
Uptime: 1,
},
{
ChannelPoint: "a:2",
MonitoredFor: 100,
Uptime: 1,
},
{
ChannelPoint: "a:3",
MonitoredFor: 100,
Uptime: 1,
},
}
tests := []struct {
name string
chanInsights []*insights.ChannelInfo
minAge time.Duration
expectedChannels map[string]bool
}{
{
name: "one filtered - monitored time",
chanInsights: chanInsights,
minAge: 15,
expectedChannels: map[string]bool{
"a:1": true,
"a:2": true,
"a:3": true,
},
},
{
name: "all channels included",
chanInsights: chanInsights,
minAge: 5,
expectedChannels: map[string]bool{
"a:0": true,
"a:1": true,
"a:2": true,
"a:3": true,
},
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
filtered := filterChannels(test.chanInsights, test.minAge)
if len(test.expectedChannels) != len(filtered) {
t.Fatalf("expected: %v channels, got: %v",
len(test.expectedChannels),
len(filtered))
}
for _, filteredChan := range filtered {
_, ok := test.expectedChannels[filteredChan.ChannelPoint]
if !ok {
t.Fatalf("unexpected channel: %v",
filteredChan)
}
}
})
}
}
// TestGetConfirmationScaledDataset tests scaling of data by the number of
// confirmations that a channel has.
func TestGetConfirmationScaledDataset(t *testing.T) {
tests := []struct {
name string
insights []*insights.ChannelInfo
getValue perConfirmationValue
expectedValues map[string]float64
}{
{
name: "no channels",
getValue: revenueValue,
insights: []*insights.ChannelInfo{},
},
{
name: "revenue scaled",
getValue: revenueValue,
insights: []*insights.ChannelInfo{
{
ChannelPoint: "a:0",
FeesEarned: 7,
Confirmations: 2,
},
{
ChannelPoint: "a:1",
FeesEarned: 10,
Confirmations: 1,
},
},
expectedValues: map[string]float64{
"a:0": 3.5,
"a:1": 10,
},
},
{
name: "total volume",
getValue: totalVolumeValue,
insights: []*insights.ChannelInfo{
{
ChannelPoint: "a:0",
VolumeIncoming: 10,
VolumeOutgoing: 2,
Confirmations: 2,
},
},
expectedValues: map[string]float64{
"a:0": 6,
},
},
{
name: "incoming volume",
getValue: incomingVolumeValue,
insights: []*insights.ChannelInfo{
{
ChannelPoint: "a:0",
VolumeIncoming: 10,
VolumeOutgoing: 2,
Confirmations: 2,
},
},
expectedValues: map[string]float64{
"a:0": 5,
},
},
{
name: "outgoing volume",
getValue: outgoingVolumeValue,
insights: []*insights.ChannelInfo{
{
ChannelPoint: "a:0",
VolumeIncoming: 10,
VolumeOutgoing: 2,
Confirmations: 2,
},
},
expectedValues: map[string]float64{
"a:0": 1,
},
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
data := getConfirmationScaledDataset(
test.getValue, test.insights,
)
if len(data) != len(test.expectedValues) {
t.Fatalf("expected: %v, got: %v",
len(test.expectedValues), len(data))
}
for chanPoint, value := range test.expectedValues {
if data.Value(chanPoint) != value {
t.Fatalf("expected: %v to "+
"have value %v, got %v", chanPoint,
value, data.Value(chanPoint))
}
}
})

View file

@ -2,49 +2,87 @@ package trmrpc
import (
"context"
"sort"
"time"
"github.com/lightninglabs/terminator/insights"
"github.com/lightninglabs/terminator/recommend"
)
// parseRequest parses a rpc close recommendation request and returns the
// close recommendation config that the request requires.
func parseRequest(ctx context.Context, cfg *Config,
req *CloseRecommendationsRequest) *recommend.CloseRecommendationConfig {
// parseRecommendationRequest parses a close recommendation request and
// returns the config required to get recommendations.
func parseRecommendationRequest(ctx context.Context, cfg *Config,
req *CloseRecommendationRequest) *recommend.CloseRecommendationConfig {
// Create a close recommendations config with the minimum monitored
// value provided in the request and the default outlier multiplier.
recConfig := &recommend.CloseRecommendationConfig{
OpenChannels: cfg.wrapListChannels(ctx, true),
recCfg := &recommend.CloseRecommendationConfig{
ChannelInsights: func() ([]*insights.ChannelInfo, error) {
return channelInsights(ctx, cfg)
},
MinimumMonitored: time.Second *
time.Duration(req.MinimumMonitored),
OutlierMultiplier: recommend.DefaultOutlierMultiplier,
}
// If a non-zero outlier multiple was provided, set it on the config.
if req.OutlierMultiplier != 0 {
recConfig.OutlierMultiplier = float64(req.OutlierMultiplier)
// Get the metric that the recommendations are being calculated based
// on.
switch req.Metric {
case CloseRecommendationRequest_UPTIME:
recCfg.Metric = recommend.UptimeMetric
case CloseRecommendationRequest_REVENUE:
recCfg.Metric = recommend.RevenueMetric
case CloseRecommendationRequest_INCOMING_VOLUME:
recCfg.Metric = recommend.IncomingVolume
case CloseRecommendationRequest_OUTGOING_VOLUME:
recCfg.Metric = recommend.OutgoingVolume
case CloseRecommendationRequest_TOTAL_VOLUME:
recCfg.Metric = recommend.Volume
}
threshold, ok := req.Threshold.(*CloseRecommendationsRequest_UptimeThreshold)
if ok {
recConfig.UptimeThreshold = float64(threshold.UptimeThreshold)
}
return recConfig
return recCfg
}
// parseResponse parses the response obtained getting a close recommendation
// parseOutlierRequest parses a rpc outlier recommendation request and returns
// the close recommendation config and multiplier required.
func parseOutlierRequest(ctx context.Context, cfg *Config,
req *OutlierRecommendationsRequest) (
*recommend.CloseRecommendationConfig, float64) {
multiplier := recommend.DefaultOutlierMultiplier
if req.OutlierMultiplier != 0 {
multiplier = float64(req.OutlierMultiplier)
}
return parseRecommendationRequest(ctx, cfg, req.RecRequest), multiplier
}
// parseThresholdRequest parses a rpc threshold recommendation request and
// returns the close recommendation config and threshold required. The above
// threshold boolean is inverted to allow for
// a default that returns values below a threshold.
func parseThresholdRequest(ctx context.Context, cfg *Config,
req *ThresholdRecommendationsRequest) (
*recommend.CloseRecommendationConfig, float64) {
return parseRecommendationRequest(ctx, cfg, req.RecRequest),
float64(req.ThresholdValue)
}
// rpcResponse parses the response obtained getting a close recommendation
// and converts it to a close recommendation response.
func parseResponse(report *recommend.Report) *CloseRecommendationsResponse {
func rpcResponse(report *recommend.Report) *CloseRecommendationsResponse {
resp := &CloseRecommendationsResponse{
TotalChannels: int32(report.TotalChannels),
ConsideredChannels: int32(report.ConsideredChannels),
}
for chanPoint, rec := range report.OutlierRecommendations {
resp.OutlierRecommendations = append(
resp.OutlierRecommendations, &Recommendation{
for chanPoint, rec := range report.Recommendations {
resp.Recommendations = append(
resp.Recommendations, &Recommendation{
ChanPoint: chanPoint,
Value: float32(rec.Value),
RecommendClose: rec.RecommendClose,
@ -52,15 +90,11 @@ func parseResponse(report *recommend.Report) *CloseRecommendationsResponse {
)
}
for chanPoint, rec := range report.ThresholdRecommendations {
resp.ThresholdRecommendations = append(
resp.ThresholdRecommendations, &Recommendation{
ChanPoint: chanPoint,
Value: float32(rec.Value),
RecommendClose: rec.RecommendClose,
},
)
}
// Sort the recommendations returned by value.
sort.SliceStable(resp.Recommendations, func(i, j int) bool {
return resp.Recommendations[i].Value <
resp.Recommendations[j].Value
})
return resp
}

View file

@ -23,101 +23,228 @@ var _ = math.Inf
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
type CloseRecommendationsRequest struct {
type CloseRecommendationRequest_Metric int32
const (
CloseRecommendationRequest_UNKNOWN CloseRecommendationRequest_Metric = 0
CloseRecommendationRequest_UPTIME CloseRecommendationRequest_Metric = 1
CloseRecommendationRequest_REVENUE CloseRecommendationRequest_Metric = 2
CloseRecommendationRequest_INCOMING_VOLUME CloseRecommendationRequest_Metric = 3
CloseRecommendationRequest_OUTGOING_VOLUME CloseRecommendationRequest_Metric = 4
CloseRecommendationRequest_TOTAL_VOLUME CloseRecommendationRequest_Metric = 5
)
var CloseRecommendationRequest_Metric_name = map[int32]string{
0: "UNKNOWN",
1: "UPTIME",
2: "REVENUE",
3: "INCOMING_VOLUME",
4: "OUTGOING_VOLUME",
5: "TOTAL_VOLUME",
}
var CloseRecommendationRequest_Metric_value = map[string]int32{
"UNKNOWN": 0,
"UPTIME": 1,
"REVENUE": 2,
"INCOMING_VOLUME": 3,
"OUTGOING_VOLUME": 4,
"TOTAL_VOLUME": 5,
}
func (x CloseRecommendationRequest_Metric) String() string {
return proto.EnumName(CloseRecommendationRequest_Metric_name, int32(x))
}
func (CloseRecommendationRequest_Metric) EnumDescriptor() ([]byte, []int) {
return fileDescriptor_77a6da22d6a3feb1, []int{0, 0}
}
type CloseRecommendationRequest struct {
//
//The minimum amount of time in seconds that a channel should have been
//monitored by lnd to be eligible for close. This value is in place to
//protect against closing of newer channels.
MinimumMonitored int64 `protobuf:"varint,1,opt,name=minimum_monitored,json=minimumMonitored,proto3" json:"minimum_monitored,omitempty"`
//
//The number of inter-quartile ranges a value needs to be beneath the lower
//quartile/ above the upper quartile to be considered a lower/upper outlier.
//Lower values will be more aggressive in recommending channel closes, and
//upper values will be more conservative. Recommended values are 1.5 for
//aggressive recommendations and 3 for conservative recommendations.
OutlierMultiplier float32 `protobuf:"fixed32,2,opt,name=outlier_multiplier,json=outlierMultiplier,proto3" json:"outlier_multiplier,omitempty"`
//
//Threshold contains the threshold value that is used to recommend channels
//for closure.
//
// Types that are valid to be assigned to Threshold:
// *CloseRecommendationsRequest_UptimeThreshold
Threshold isCloseRecommendationsRequest_Threshold `protobuf_oneof:"threshold"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
//The data point base close recommendations on. Available options are:
//Uptime: ratio of channel peer's uptime to the period they have been
//monitored to.
//Revenue: the revenue that the channel has produced per block that its
//funding transaction has been confirmed for.
Metric CloseRecommendationRequest_Metric `protobuf:"varint,2,opt,name=metric,proto3,enum=trmrpc.CloseRecommendationRequest_Metric" json:"metric,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *CloseRecommendationsRequest) Reset() { *m = CloseRecommendationsRequest{} }
func (m *CloseRecommendationsRequest) String() string { return proto.CompactTextString(m) }
func (*CloseRecommendationsRequest) ProtoMessage() {}
func (*CloseRecommendationsRequest) Descriptor() ([]byte, []int) {
func (m *CloseRecommendationRequest) Reset() { *m = CloseRecommendationRequest{} }
func (m *CloseRecommendationRequest) String() string { return proto.CompactTextString(m) }
func (*CloseRecommendationRequest) ProtoMessage() {}
func (*CloseRecommendationRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_77a6da22d6a3feb1, []int{0}
}
func (m *CloseRecommendationsRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_CloseRecommendationsRequest.Unmarshal(m, b)
func (m *CloseRecommendationRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_CloseRecommendationRequest.Unmarshal(m, b)
}
func (m *CloseRecommendationsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_CloseRecommendationsRequest.Marshal(b, m, deterministic)
func (m *CloseRecommendationRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_CloseRecommendationRequest.Marshal(b, m, deterministic)
}
func (m *CloseRecommendationsRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_CloseRecommendationsRequest.Merge(m, src)
func (m *CloseRecommendationRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_CloseRecommendationRequest.Merge(m, src)
}
func (m *CloseRecommendationsRequest) XXX_Size() int {
return xxx_messageInfo_CloseRecommendationsRequest.Size(m)
func (m *CloseRecommendationRequest) XXX_Size() int {
return xxx_messageInfo_CloseRecommendationRequest.Size(m)
}
func (m *CloseRecommendationsRequest) XXX_DiscardUnknown() {
xxx_messageInfo_CloseRecommendationsRequest.DiscardUnknown(m)
func (m *CloseRecommendationRequest) XXX_DiscardUnknown() {
xxx_messageInfo_CloseRecommendationRequest.DiscardUnknown(m)
}
var xxx_messageInfo_CloseRecommendationsRequest proto.InternalMessageInfo
var xxx_messageInfo_CloseRecommendationRequest proto.InternalMessageInfo
func (m *CloseRecommendationsRequest) GetMinimumMonitored() int64 {
func (m *CloseRecommendationRequest) GetMinimumMonitored() int64 {
if m != nil {
return m.MinimumMonitored
}
return 0
}
func (m *CloseRecommendationsRequest) GetOutlierMultiplier() float32 {
func (m *CloseRecommendationRequest) GetMetric() CloseRecommendationRequest_Metric {
if m != nil {
return m.Metric
}
return CloseRecommendationRequest_UNKNOWN
}
type OutlierRecommendationsRequest struct {
//
//The parameters that are common to all close recommendations.
RecRequest *CloseRecommendationRequest `protobuf:"bytes,1,opt,name=rec_request,json=recRequest,proto3" json:"rec_request,omitempty"`
//
//The number of inter-quartile ranges a value needs to be beneath the lower
//quartile/ above the upper quartile to be considered a lower/upper outlier.
//Lower values will be more aggressive in recommending channel closes, and
//upper values will be more conservative. Recommended values are 1.5 for
//aggressive recommendations and 3 for conservative recommendations.
OutlierMultiplier float32 `protobuf:"fixed32,2,opt,name=outlier_multiplier,json=outlierMultiplier,proto3" json:"outlier_multiplier,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *OutlierRecommendationsRequest) Reset() { *m = OutlierRecommendationsRequest{} }
func (m *OutlierRecommendationsRequest) String() string { return proto.CompactTextString(m) }
func (*OutlierRecommendationsRequest) ProtoMessage() {}
func (*OutlierRecommendationsRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_77a6da22d6a3feb1, []int{1}
}
func (m *OutlierRecommendationsRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_OutlierRecommendationsRequest.Unmarshal(m, b)
}
func (m *OutlierRecommendationsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_OutlierRecommendationsRequest.Marshal(b, m, deterministic)
}
func (m *OutlierRecommendationsRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_OutlierRecommendationsRequest.Merge(m, src)
}
func (m *OutlierRecommendationsRequest) XXX_Size() int {
return xxx_messageInfo_OutlierRecommendationsRequest.Size(m)
}
func (m *OutlierRecommendationsRequest) XXX_DiscardUnknown() {
xxx_messageInfo_OutlierRecommendationsRequest.DiscardUnknown(m)
}
var xxx_messageInfo_OutlierRecommendationsRequest proto.InternalMessageInfo
func (m *OutlierRecommendationsRequest) GetRecRequest() *CloseRecommendationRequest {
if m != nil {
return m.RecRequest
}
return nil
}
func (m *OutlierRecommendationsRequest) GetOutlierMultiplier() float32 {
if m != nil {
return m.OutlierMultiplier
}
return 0
}
type isCloseRecommendationsRequest_Threshold interface {
isCloseRecommendationsRequest_Threshold()
type ThresholdRecommendationsRequest struct {
//
//The parameters that are common to all close recommendations.
RecRequest *CloseRecommendationRequest `protobuf:"bytes,1,opt,name=rec_request,json=recRequest,proto3" json:"rec_request,omitempty"`
//
//The threshold that recommendations will be calculated based on.
//For uptime: ratio of uptime to observed lifetime beneath which channels
//will be recommended for closure.
//
//For revenue: revenue per block that capital has been committed to the
//channel beneath which channels will be recommended for closure. This
//value is provided per block so that channels that have been open for
//different periods of time can be compared.
//
//For incoming volume: The incoming volume per block that capital has
//been committed to the channel beneath which channels will be recommended
//for closure. This value is provided per block so that channels that have
//been open for different periods of time can be compared.
//
//For outgoing volume: The outgoing volume per block that capital has been
//committed to the channel beneath which channels will be recommended for
//closure. This value is provided per block so that channels that have been
//open for different periods of time can be compared.
//
//For total volume: The total volume per block that capital has been
//committed to the channel beneath which channels will be recommended for
//closure. This value is provided per block so that channels that have been
//open for different periods of time can be compared.
ThresholdValue float32 `protobuf:"fixed32,2,opt,name=threshold_value,json=thresholdValue,proto3" json:"threshold_value,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
type CloseRecommendationsRequest_UptimeThreshold struct {
UptimeThreshold float32 `protobuf:"fixed32,3,opt,name=uptime_threshold,json=uptimeThreshold,proto3,oneof"`
func (m *ThresholdRecommendationsRequest) Reset() { *m = ThresholdRecommendationsRequest{} }
func (m *ThresholdRecommendationsRequest) String() string { return proto.CompactTextString(m) }
func (*ThresholdRecommendationsRequest) ProtoMessage() {}
func (*ThresholdRecommendationsRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_77a6da22d6a3feb1, []int{2}
}
func (*CloseRecommendationsRequest_UptimeThreshold) isCloseRecommendationsRequest_Threshold() {}
func (m *ThresholdRecommendationsRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ThresholdRecommendationsRequest.Unmarshal(m, b)
}
func (m *ThresholdRecommendationsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_ThresholdRecommendationsRequest.Marshal(b, m, deterministic)
}
func (m *ThresholdRecommendationsRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_ThresholdRecommendationsRequest.Merge(m, src)
}
func (m *ThresholdRecommendationsRequest) XXX_Size() int {
return xxx_messageInfo_ThresholdRecommendationsRequest.Size(m)
}
func (m *ThresholdRecommendationsRequest) XXX_DiscardUnknown() {
xxx_messageInfo_ThresholdRecommendationsRequest.DiscardUnknown(m)
}
func (m *CloseRecommendationsRequest) GetThreshold() isCloseRecommendationsRequest_Threshold {
var xxx_messageInfo_ThresholdRecommendationsRequest proto.InternalMessageInfo
func (m *ThresholdRecommendationsRequest) GetRecRequest() *CloseRecommendationRequest {
if m != nil {
return m.Threshold
return m.RecRequest
}
return nil
}
func (m *CloseRecommendationsRequest) GetUptimeThreshold() float32 {
if x, ok := m.GetThreshold().(*CloseRecommendationsRequest_UptimeThreshold); ok {
return x.UptimeThreshold
func (m *ThresholdRecommendationsRequest) GetThresholdValue() float32 {
if m != nil {
return m.ThresholdValue
}
return 0
}
// XXX_OneofWrappers is for the internal use of the proto package.
func (*CloseRecommendationsRequest) XXX_OneofWrappers() []interface{} {
return []interface{}{
(*CloseRecommendationsRequest_UptimeThreshold)(nil),
}
}
type CloseRecommendationsResponse struct {
//
//The total number of channels, before filtering out channels that are
@ -127,29 +254,21 @@ type CloseRecommendationsResponse struct {
//The number of channels that were considered for close recommendations.
ConsideredChannels int32 `protobuf:"varint,2,opt,name=considered_channels,json=consideredChannels,proto3" json:"considered_channels,omitempty"`
//
//A map of channels to close recommendations, based out whether they are
//outliers in the uptime dataset. The absence of a channel in this set
//implies that it was not considered for close because it did not meet
//the criteria for close (it is private, or has not been monitored for
//long enough to make a decision).
OutlierRecommendations []*Recommendation `protobuf:"bytes,3,rep,name=outlier_recommendations,json=outlierRecommendations,proto3" json:"outlier_recommendations,omitempty"`
//
//A set of channel close recommendations, based out whether they are
//beneath the threshold provided in the request. The absence of a channel
//in this set implies that it was not considered for close because it
//did not meet the criteria for close (it is private, or has not been
//monitored for long enough to make a decision).
ThresholdRecommendations []*Recommendation `protobuf:"bytes,4,rep,name=threshold_recommendations,json=thresholdRecommendations,proto3" json:"threshold_recommendations,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
//A set of channel close recommendations. The absence of a channel in this
//set implies that it was not considered for close because it did not meet
//the criteria for close recommendations (it is private, or has not been
//monitored for long enough).
Recommendations []*Recommendation `protobuf:"bytes,3,rep,name=recommendations,proto3" json:"recommendations,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *CloseRecommendationsResponse) Reset() { *m = CloseRecommendationsResponse{} }
func (m *CloseRecommendationsResponse) String() string { return proto.CompactTextString(m) }
func (*CloseRecommendationsResponse) ProtoMessage() {}
func (*CloseRecommendationsResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_77a6da22d6a3feb1, []int{1}
return fileDescriptor_77a6da22d6a3feb1, []int{3}
}
func (m *CloseRecommendationsResponse) XXX_Unmarshal(b []byte) error {
@ -184,16 +303,9 @@ func (m *CloseRecommendationsResponse) GetConsideredChannels() int32 {
return 0
}
func (m *CloseRecommendationsResponse) GetOutlierRecommendations() []*Recommendation {
func (m *CloseRecommendationsResponse) GetRecommendations() []*Recommendation {
if m != nil {
return m.OutlierRecommendations
}
return nil
}
func (m *CloseRecommendationsResponse) GetThresholdRecommendations() []*Recommendation {
if m != nil {
return m.ThresholdRecommendations
return m.Recommendations
}
return nil
}
@ -216,7 +328,7 @@ func (m *Recommendation) Reset() { *m = Recommendation{} }
func (m *Recommendation) String() string { return proto.CompactTextString(m) }
func (*Recommendation) ProtoMessage() {}
func (*Recommendation) Descriptor() ([]byte, []int) {
return fileDescriptor_77a6da22d6a3feb1, []int{2}
return fileDescriptor_77a6da22d6a3feb1, []int{4}
}
func (m *Recommendation) XXX_Unmarshal(b []byte) error {
@ -282,7 +394,7 @@ func (m *RevenueReportRequest) Reset() { *m = RevenueReportRequest{} }
func (m *RevenueReportRequest) String() string { return proto.CompactTextString(m) }
func (*RevenueReportRequest) ProtoMessage() {}
func (*RevenueReportRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_77a6da22d6a3feb1, []int{3}
return fileDescriptor_77a6da22d6a3feb1, []int{5}
}
func (m *RevenueReportRequest) XXX_Unmarshal(b []byte) error {
@ -338,7 +450,7 @@ func (m *RevenueReportResponse) Reset() { *m = RevenueReportResponse{} }
func (m *RevenueReportResponse) String() string { return proto.CompactTextString(m) }
func (*RevenueReportResponse) ProtoMessage() {}
func (*RevenueReportResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_77a6da22d6a3feb1, []int{4}
return fileDescriptor_77a6da22d6a3feb1, []int{6}
}
func (m *RevenueReportResponse) XXX_Unmarshal(b []byte) error {
@ -386,7 +498,7 @@ func (m *RevenueReport) Reset() { *m = RevenueReport{} }
func (m *RevenueReport) String() string { return proto.CompactTextString(m) }
func (*RevenueReport) ProtoMessage() {}
func (*RevenueReport) Descriptor() ([]byte, []int) {
return fileDescriptor_77a6da22d6a3feb1, []int{5}
return fileDescriptor_77a6da22d6a3feb1, []int{7}
}
func (m *RevenueReport) XXX_Unmarshal(b []byte) error {
@ -449,7 +561,7 @@ func (m *PairReport) Reset() { *m = PairReport{} }
func (m *PairReport) String() string { return proto.CompactTextString(m) }
func (*PairReport) ProtoMessage() {}
func (*PairReport) Descriptor() ([]byte, []int) {
return fileDescriptor_77a6da22d6a3feb1, []int{6}
return fileDescriptor_77a6da22d6a3feb1, []int{8}
}
func (m *PairReport) XXX_Unmarshal(b []byte) error {
@ -508,7 +620,7 @@ func (m *ChannelInsightsRequest) Reset() { *m = ChannelInsightsRequest{}
func (m *ChannelInsightsRequest) String() string { return proto.CompactTextString(m) }
func (*ChannelInsightsRequest) ProtoMessage() {}
func (*ChannelInsightsRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_77a6da22d6a3feb1, []int{7}
return fileDescriptor_77a6da22d6a3feb1, []int{9}
}
func (m *ChannelInsightsRequest) XXX_Unmarshal(b []byte) error {
@ -541,7 +653,7 @@ func (m *ChannelInsightsResponse) Reset() { *m = ChannelInsightsResponse
func (m *ChannelInsightsResponse) String() string { return proto.CompactTextString(m) }
func (*ChannelInsightsResponse) ProtoMessage() {}
func (*ChannelInsightsResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_77a6da22d6a3feb1, []int{8}
return fileDescriptor_77a6da22d6a3feb1, []int{10}
}
func (m *ChannelInsightsResponse) XXX_Unmarshal(b []byte) error {
@ -606,7 +718,7 @@ func (m *ChannelInsight) Reset() { *m = ChannelInsight{} }
func (m *ChannelInsight) String() string { return proto.CompactTextString(m) }
func (*ChannelInsight) ProtoMessage() {}
func (*ChannelInsight) Descriptor() ([]byte, []int) {
return fileDescriptor_77a6da22d6a3feb1, []int{9}
return fileDescriptor_77a6da22d6a3feb1, []int{11}
}
func (m *ChannelInsight) XXX_Unmarshal(b []byte) error {
@ -684,7 +796,10 @@ func (m *ChannelInsight) GetPrivate() bool {
}
func init() {
proto.RegisterType((*CloseRecommendationsRequest)(nil), "trmrpc.CloseRecommendationsRequest")
proto.RegisterEnum("trmrpc.CloseRecommendationRequest_Metric", CloseRecommendationRequest_Metric_name, CloseRecommendationRequest_Metric_value)
proto.RegisterType((*CloseRecommendationRequest)(nil), "trmrpc.CloseRecommendationRequest")
proto.RegisterType((*OutlierRecommendationsRequest)(nil), "trmrpc.OutlierRecommendationsRequest")
proto.RegisterType((*ThresholdRecommendationsRequest)(nil), "trmrpc.ThresholdRecommendationsRequest")
proto.RegisterType((*CloseRecommendationsResponse)(nil), "trmrpc.CloseRecommendationsResponse")
proto.RegisterType((*Recommendation)(nil), "trmrpc.Recommendation")
proto.RegisterType((*RevenueReportRequest)(nil), "trmrpc.RevenueReportRequest")
@ -700,58 +815,66 @@ func init() {
func init() { proto.RegisterFile("rpc.proto", fileDescriptor_77a6da22d6a3feb1) }
var fileDescriptor_77a6da22d6a3feb1 = []byte{
// 814 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x55, 0xcd, 0x6e, 0xdb, 0x46,
0x10, 0xae, 0x28, 0xff, 0x69, 0x54, 0xc9, 0xca, 0x56, 0x71, 0x14, 0xd7, 0x69, 0x0c, 0x36, 0x6d,
0x05, 0xa4, 0xb5, 0x03, 0xf7, 0x52, 0xf4, 0xd6, 0x06, 0x01, 0x62, 0xa0, 0x46, 0x82, 0xb5, 0x8f,
0x05, 0x88, 0x2d, 0x35, 0x91, 0x17, 0xe5, 0xee, 0x32, 0xbb, 0x4b, 0x01, 0x79, 0x92, 0x5e, 0xfa,
0x16, 0x7d, 0x8d, 0x5e, 0x7a, 0xe8, 0xfb, 0x14, 0xfb, 0x43, 0x52, 0x54, 0x64, 0xf7, 0x46, 0x7e,
0xdf, 0xb7, 0x33, 0xc3, 0x99, 0x6f, 0x87, 0x30, 0xd0, 0x65, 0x7e, 0x56, 0x6a, 0x65, 0x15, 0xd9,
0xb3, 0x5a, 0xe8, 0x32, 0x3f, 0x3e, 0x59, 0x2a, 0xb5, 0x2c, 0xf0, 0x9c, 0x95, 0xfc, 0x9c, 0x49,
0xa9, 0x2c, 0xb3, 0x5c, 0x49, 0x13, 0x54, 0xe9, 0x5f, 0x3d, 0xf8, 0xfc, 0x65, 0xa1, 0x0c, 0x52,
0xcc, 0x95, 0x10, 0x28, 0x17, 0x81, 0xa6, 0xf8, 0xbe, 0x42, 0x63, 0xc9, 0x73, 0x78, 0x20, 0xb8,
0xe4, 0xa2, 0x12, 0x99, 0x50, 0x92, 0x5b, 0xa5, 0x71, 0x31, 0xeb, 0x9d, 0xf6, 0xe6, 0x7d, 0x3a,
0x89, 0xc4, 0x55, 0x8d, 0x93, 0xef, 0x80, 0xa8, 0xca, 0x16, 0x1c, 0x75, 0x26, 0xaa, 0xc2, 0xf2,
0xd2, 0x3d, 0xce, 0x92, 0xd3, 0xde, 0x3c, 0xa1, 0x0f, 0x22, 0x73, 0xd5, 0x10, 0xe4, 0x39, 0x4c,
0xaa, 0xd2, 0x72, 0x81, 0x99, 0xbd, 0xd5, 0x68, 0x6e, 0x55, 0xb1, 0x98, 0xf5, 0x9d, 0xf8, 0xf5,
0x27, 0xf4, 0x30, 0x30, 0x37, 0x35, 0xf1, 0xf3, 0x10, 0x06, 0x8d, 0x2a, 0xfd, 0x33, 0x81, 0x93,
0xed, 0x55, 0x9b, 0x52, 0x49, 0x83, 0xe4, 0x2b, 0x18, 0x5b, 0x65, 0x59, 0x91, 0xe5, 0xb7, 0x4c,
0x4a, 0x2c, 0x8c, 0xaf, 0x79, 0x97, 0x8e, 0x3c, 0xfa, 0x32, 0x82, 0xe4, 0x1c, 0x3e, 0xcb, 0x95,
0x34, 0x7c, 0x81, 0x1a, 0x17, 0xad, 0x36, 0xf1, 0x5a, 0xd2, 0x52, 0xcd, 0x81, 0x37, 0xf0, 0xa8,
0xfe, 0x42, 0xdd, 0x4d, 0x3d, 0xeb, 0x9f, 0xf6, 0xe7, 0xc3, 0x8b, 0xa3, 0xb3, 0xd0, 0xf6, 0xb3,
0x6e, 0x65, 0xf4, 0x28, 0x1e, 0xdb, 0x28, 0x98, 0x5c, 0xc3, 0xe3, 0xe6, 0xb3, 0x3e, 0x0a, 0xb9,
0x73, 0x6f, 0xc8, 0x59, 0x73, 0x70, 0x23, 0x68, 0x2a, 0x61, 0xdc, 0x85, 0xc8, 0x13, 0x00, 0xf7,
0x75, 0x59, 0xa9, 0xb8, 0xb4, 0xbe, 0x17, 0x03, 0x3a, 0x70, 0xc8, 0x5b, 0x07, 0x90, 0x29, 0xec,
0xae, 0x58, 0x51, 0x61, 0x9c, 0x55, 0x78, 0x21, 0xdf, 0xc0, 0x61, 0x53, 0x51, 0x96, 0xbb, 0x76,
0xfb, 0xf1, 0x1c, 0xd0, 0x71, 0x03, 0xfb, 0x21, 0xa4, 0xef, 0x61, 0x4a, 0x71, 0x85, 0xb2, 0x42,
0x8a, 0xa5, 0xd2, 0xb6, 0x36, 0xcf, 0x53, 0x18, 0xb6, 0x59, 0xdd, 0x08, 0xfa, 0xf3, 0x01, 0x85,
0x26, 0xad, 0x71, 0x65, 0x19, 0xcb, 0xb4, 0xcd, 0xdc, 0xac, 0x7d, 0xf2, 0x1d, 0x3a, 0xf0, 0xc8,
0x0d, 0x17, 0x48, 0x1e, 0xc3, 0x81, 0x4b, 0xed, 0xc9, 0xbe, 0x27, 0xf7, 0x51, 0x2e, 0x1c, 0x95,
0xbe, 0x86, 0x87, 0x1b, 0x29, 0xe3, 0xe4, 0xcf, 0x61, 0x5f, 0x7b, 0x24, 0xe4, 0x1b, 0x5e, 0x3c,
0x6c, 0xdb, 0xb7, 0xae, 0xaf, 0x55, 0xe9, 0xbf, 0x3d, 0x18, 0x75, 0x28, 0x6f, 0x1e, 0xa6, 0x97,
0x68, 0x6b, 0x47, 0xc4, 0x86, 0x8d, 0x02, 0x1a, 0xcd, 0x40, 0x2e, 0xe1, 0xd3, 0x92, 0x71, 0x67,
0x84, 0x90, 0x2e, 0xf1, 0xe9, 0xbe, 0xde, 0x9a, 0xee, 0xec, 0x2d, 0xe3, 0x3a, 0x3c, 0x9a, 0x57,
0xd2, 0xea, 0x0f, 0x74, 0x58, 0xb6, 0xc8, 0x31, 0x85, 0xc9, 0xa6, 0x80, 0x4c, 0xa0, 0xff, 0x3b,
0x7e, 0x88, 0xa9, 0xdd, 0x23, 0x99, 0xaf, 0x4f, 0x69, 0x78, 0x41, 0xea, 0x4c, 0xed, 0xd1, 0x38,
0xb9, 0x1f, 0x93, 0x1f, 0x7a, 0xe9, 0xdf, 0x3d, 0x80, 0x96, 0x21, 0x2f, 0x60, 0xca, 0x84, 0xaa,
0xa4, 0xcd, 0x54, 0x65, 0x97, 0x8a, 0xcb, 0x65, 0x26, 0x0c, 0xb3, 0xf1, 0x2e, 0x93, 0xc0, 0xbd,
0x89, 0xd4, 0x95, 0x61, 0x96, 0x7c, 0x0b, 0xe4, 0x1d, 0xa2, 0xd9, 0xd0, 0x27, 0xe1, 0xee, 0x3b,
0xa6, 0xa3, 0x6e, 0xe3, 0x73, 0x99, 0x2b, 0xd1, 0xe8, 0xfb, 0xeb, 0xf1, 0x2f, 0x23, 0xd5, 0x89,
0xdf, 0xd5, 0xef, 0xb4, 0xf1, 0xd7, 0xd5, 0xe9, 0x0c, 0x8e, 0x62, 0xe3, 0x2f, 0xa5, 0xe1, 0xcb,
0x5b, 0x5b, 0xaf, 0xa8, 0xf4, 0x57, 0x78, 0xf4, 0x11, 0x13, 0xcd, 0xf0, 0x13, 0x4c, 0xe2, 0x08,
0x33, 0x1e, 0xb9, 0xe8, 0x8a, 0xe6, 0x52, 0x75, 0x8f, 0xd2, 0xc3, 0xbc, 0x1b, 0x2a, 0xfd, 0x27,
0x81, 0x71, 0x57, 0xf3, 0x7f, 0x97, 0xc9, 0xad, 0xcc, 0x7a, 0x25, 0x66, 0x06, 0x73, 0x25, 0x17,
0x26, 0x7a, 0x7b, 0xd2, 0x10, 0xd7, 0x01, 0x77, 0x5e, 0x8b, 0x3b, 0xb0, 0x56, 0x06, 0xa3, 0x8f,
0x02, 0x5a, 0xcb, 0x5e, 0xc0, 0x74, 0xa5, 0x8a, 0x4a, 0xe0, 0xd6, 0x6e, 0x91, 0xc0, 0x75, 0xba,
0xdb, 0x9e, 0xe8, 0xce, 0x6f, 0x77, 0xfd, 0x44, 0x67, 0x82, 0x73, 0xf0, 0x5d, 0xcf, 0x90, 0x69,
0x89, 0x8b, 0xa0, 0xde, 0xf3, 0xea, 0xb1, 0xc3, 0x5f, 0x79, 0xd8, 0x2b, 0x9f, 0xc1, 0x28, 0x57,
0xf2, 0x1d, 0xd7, 0x22, 0x2e, 0xaa, 0xfd, 0xd3, 0xde, 0x7c, 0x44, 0xbb, 0x20, 0x99, 0xc1, 0x7e,
0xa9, 0xf9, 0x8a, 0x59, 0x9c, 0x1d, 0xf8, 0xb5, 0x51, 0xbf, 0x5e, 0xfc, 0x91, 0xc0, 0xe4, 0x06,
0xb5, 0xe0, 0x92, 0x59, 0xa5, 0xaf, 0x51, 0xaf, 0x50, 0x13, 0x06, 0xd3, 0x6d, 0x2b, 0x9d, 0x7c,
0xd9, 0x4c, 0xea, 0xee, 0xdf, 0xd4, 0xf1, 0xb3, 0xfb, 0x45, 0xd1, 0x0e, 0xbf, 0x6c, 0xde, 0xf4,
0x93, 0xed, 0xbb, 0x21, 0x06, 0x7d, 0x72, 0x07, 0x1b, 0xa3, 0x51, 0x38, 0xdc, 0xf0, 0x1d, 0xf9,
0x62, 0xbb, 0xab, 0x9a, 0x32, 0x9f, 0xde, 0xc9, 0x87, 0x98, 0xbf, 0xed, 0xf9, 0xbf, 0xf2, 0xf7,
0xff, 0x05, 0x00, 0x00, 0xff, 0xff, 0x9d, 0x67, 0xb7, 0xb2, 0xc8, 0x07, 0x00, 0x00,
// 944 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x56, 0x4f, 0x6f, 0xe3, 0x44,
0x14, 0xc7, 0x71, 0x9b, 0xb6, 0x2f, 0x9b, 0xd4, 0x3b, 0xdb, 0x2d, 0xa1, 0x6a, 0x69, 0x64, 0xed,
0xb2, 0x41, 0x40, 0xba, 0x0a, 0x17, 0xc4, 0x89, 0xaa, 0x8a, 0x96, 0x88, 0x26, 0xa9, 0x66, 0x93,
0x72, 0x41, 0xb2, 0x8c, 0x33, 0x9b, 0x0e, 0xc4, 0x33, 0xde, 0xf1, 0x38, 0xd2, 0x7e, 0x01, 0xae,
0x1c, 0xf8, 0x32, 0x7c, 0x08, 0x2e, 0x1c, 0xf8, 0x2e, 0x1c, 0xd1, 0xfc, 0xb1, 0x1d, 0x87, 0x74,
0xcb, 0x85, 0x9b, 0xe7, 0xf7, 0xfb, 0xcd, 0xfb, 0xcd, 0xbc, 0xf7, 0x32, 0x2f, 0x70, 0x20, 0x92,
0xa8, 0x97, 0x08, 0x2e, 0x39, 0xaa, 0x4b, 0x11, 0x8b, 0x24, 0x3a, 0x39, 0x5d, 0x70, 0xbe, 0x58,
0x92, 0x8b, 0x30, 0xa1, 0x17, 0x21, 0x63, 0x5c, 0x86, 0x92, 0x72, 0x96, 0x1a, 0x95, 0xff, 0xb7,
0x03, 0x27, 0x57, 0x4b, 0x9e, 0x12, 0x4c, 0x22, 0x1e, 0xc7, 0x84, 0xcd, 0x35, 0x8d, 0xc9, 0xdb,
0x8c, 0xa4, 0x12, 0x7d, 0x06, 0x8f, 0x63, 0xca, 0x68, 0x9c, 0xc5, 0x41, 0xcc, 0x19, 0x95, 0x5c,
0x90, 0x79, 0xdb, 0xe9, 0x38, 0x5d, 0x17, 0x7b, 0x96, 0x18, 0xe5, 0x38, 0xba, 0x84, 0x7a, 0x4c,
0xa4, 0xa0, 0x51, 0xbb, 0xd6, 0x71, 0xba, 0xad, 0xfe, 0xa7, 0x3d, 0x73, 0x84, 0xde, 0xfd, 0x06,
0xbd, 0x91, 0xde, 0x80, 0xed, 0x46, 0xff, 0x27, 0xa8, 0x1b, 0x04, 0x35, 0x60, 0x6f, 0x36, 0xfe,
0x6e, 0x3c, 0xf9, 0x7e, 0xec, 0x7d, 0x80, 0x00, 0xea, 0xb3, 0x9b, 0xe9, 0x70, 0x34, 0xf0, 0x1c,
0x45, 0xe0, 0xc1, 0xed, 0x60, 0x3c, 0x1b, 0x78, 0x35, 0xf4, 0x04, 0x0e, 0x87, 0xe3, 0xab, 0xc9,
0x68, 0x38, 0x7e, 0x15, 0xdc, 0x4e, 0xae, 0x67, 0xa3, 0x81, 0xe7, 0x2a, 0x70, 0x32, 0x9b, 0xbe,
0x9a, 0xac, 0x81, 0x3b, 0xc8, 0x83, 0x47, 0xd3, 0xc9, 0xf4, 0xf2, 0x3a, 0x47, 0x76, 0xfd, 0xdf,
0x1c, 0x38, 0x9b, 0x64, 0x72, 0x49, 0x89, 0xa8, 0x9e, 0x2d, 0xcd, 0x6f, 0x7f, 0x05, 0x0d, 0x41,
0xa2, 0x40, 0x98, 0xa5, 0xbe, 0x77, 0xa3, 0xef, 0x3f, 0x7c, 0x2b, 0x0c, 0x82, 0x44, 0x79, 0x90,
0x2f, 0x00, 0x71, 0xe3, 0x12, 0xc4, 0xd9, 0x52, 0xd2, 0x44, 0x7d, 0xea, 0x0c, 0xd5, 0xf0, 0x63,
0xcb, 0x8c, 0x0a, 0xc2, 0xff, 0xd5, 0x81, 0xf3, 0xe9, 0x9d, 0x20, 0xe9, 0x1d, 0x5f, 0xce, 0xff,
0xcf, 0x73, 0xbd, 0x80, 0x43, 0x99, 0xfb, 0x04, 0xab, 0x70, 0x99, 0x11, 0x7b, 0xa8, 0x56, 0x01,
0xdf, 0x2a, 0xd4, 0xff, 0xdd, 0x81, 0xd3, 0x2d, 0x31, 0x53, 0x4c, 0xd2, 0x84, 0xb3, 0x94, 0xa0,
0xe7, 0xd0, 0x92, 0x5c, 0x86, 0xcb, 0x20, 0xba, 0x0b, 0x19, 0x23, 0xcb, 0x54, 0x9f, 0x68, 0x17,
0x37, 0x35, 0x7a, 0x65, 0x41, 0x74, 0x01, 0x4f, 0x22, 0xce, 0x52, 0x3a, 0x27, 0x82, 0xcc, 0x4b,
0x6d, 0x4d, 0x6b, 0x51, 0x49, 0x15, 0x1b, 0xbe, 0x81, 0x43, 0x51, 0xb5, 0x6c, 0xbb, 0x1d, 0xb7,
0xdb, 0xe8, 0x1f, 0xe7, 0x57, 0xdd, 0xb8, 0xe5, 0xa6, 0xdc, 0x67, 0xd0, 0xaa, 0x4a, 0xd0, 0x19,
0x80, 0x72, 0x0e, 0x12, 0x4e, 0x99, 0xc9, 0xdc, 0x01, 0x3e, 0x50, 0xc8, 0x8d, 0x02, 0xd0, 0x11,
0xec, 0xae, 0xa7, 0xc2, 0x2c, 0x54, 0xaa, 0x8a, 0xc8, 0x41, 0xa4, 0x52, 0xd1, 0x76, 0x3b, 0x4e,
0x77, 0x1f, 0xb7, 0x0a, 0x58, 0x27, 0xc8, 0x7f, 0x0b, 0x47, 0x98, 0xac, 0x08, 0xcb, 0x08, 0x26,
0x09, 0x17, 0x32, 0xcf, 0xf5, 0x39, 0x34, 0x4a, 0x57, 0x95, 0x1e, 0xb7, 0x7b, 0x80, 0xa1, 0xb0,
0x4d, 0xd5, 0xb1, 0x52, 0x19, 0x0a, 0x19, 0x48, 0x1a, 0x1b, 0xf3, 0x1d, 0x7c, 0xa0, 0x91, 0x29,
0x8d, 0x09, 0xfa, 0x08, 0xf6, 0x95, 0xb5, 0x26, 0x5d, 0x4d, 0xee, 0x11, 0x36, 0x57, 0x94, 0xff,
0x2d, 0x3c, 0xdd, 0xb0, 0xb4, 0x55, 0xb9, 0x80, 0x3d, 0xa1, 0x11, 0xe3, 0xd7, 0xe8, 0x3f, 0x2d,
0xb3, 0xb6, 0xae, 0xcf, 0x55, 0xfe, 0x5f, 0x0e, 0x34, 0x2b, 0x94, 0x2e, 0x6c, 0x28, 0x16, 0x44,
0xe6, 0xd5, 0xb2, 0x09, 0x6b, 0x1a, 0xd4, 0x16, 0x0a, 0x0d, 0xe1, 0x51, 0x12, 0x52, 0x11, 0xe4,
0x76, 0x35, 0x6d, 0xf7, 0xc9, 0x56, 0xbb, 0xde, 0x4d, 0x48, 0x85, 0xf9, 0x4c, 0x07, 0x4c, 0x8a,
0x77, 0xb8, 0x91, 0x94, 0xc8, 0x09, 0x06, 0x6f, 0x53, 0x80, 0x3c, 0x70, 0x7f, 0x26, 0xef, 0xac,
0xb5, 0xfa, 0x44, 0xdd, 0xf5, 0x2a, 0x35, 0xfa, 0x28, 0x77, 0x2a, 0xb7, 0xda, 0xca, 0x7d, 0x5d,
0xfb, 0xca, 0xf1, 0xff, 0x70, 0x00, 0x4a, 0x06, 0xbd, 0x84, 0xa3, 0x30, 0xe6, 0x19, 0x93, 0x01,
0xcf, 0xe4, 0x82, 0x53, 0xb6, 0x08, 0xe2, 0x34, 0x94, 0xf6, 0x55, 0x43, 0x86, 0x9b, 0x58, 0x6a,
0x94, 0x86, 0x12, 0x7d, 0x0e, 0xe8, 0x0d, 0x21, 0xe9, 0x86, 0xbe, 0x66, 0x5e, 0x41, 0xc5, 0x54,
0xd4, 0x65, 0x7c, 0xca, 0x22, 0x1e, 0x17, 0x7a, 0x77, 0x3d, 0xfe, 0xd0, 0x52, 0x95, 0xf8, 0x55,
0xfd, 0x4e, 0x19, 0x7f, 0x5d, 0xed, 0xb7, 0xe1, 0xd8, 0x26, 0x7e, 0xc8, 0x52, 0xba, 0xb8, 0x93,
0xf9, 0xb3, 0xe0, 0xff, 0x00, 0x1f, 0xfe, 0x8b, 0xb1, 0xcd, 0x70, 0x09, 0x9e, 0x2d, 0x61, 0x40,
0x2d, 0x67, 0xbb, 0xa2, 0xf8, 0x2d, 0x55, 0xb7, 0xe2, 0xc3, 0xa8, 0x1a, 0xca, 0xff, 0xb3, 0x06,
0xad, 0xaa, 0xe6, 0xa1, 0x1f, 0x93, 0x1a, 0x1e, 0xf9, 0x70, 0x08, 0x52, 0x12, 0x71, 0x36, 0x4f,
0x6d, 0x6f, 0x7b, 0x05, 0xf1, 0xda, 0xe0, 0xaa, 0xd7, 0xb2, 0x44, 0x35, 0x78, 0xa1, 0x34, 0x8d,
0xde, 0x34, 0x68, 0x2e, 0x7b, 0x09, 0x47, 0x2b, 0xbe, 0xcc, 0x62, 0xb2, 0x35, 0x5b, 0xc8, 0x70,
0x95, 0xec, 0x96, 0x3b, 0xaa, 0xf5, 0xdb, 0x5d, 0xdf, 0x51, 0xa9, 0x60, 0x17, 0x74, 0xd6, 0x03,
0x12, 0x0a, 0x46, 0xe6, 0x46, 0x5d, 0xd7, 0xea, 0x96, 0xc2, 0x07, 0x1a, 0xd6, 0xca, 0x67, 0xd0,
0x8c, 0x38, 0x7b, 0x43, 0x45, 0x6c, 0xdf, 0xa7, 0xbd, 0x8e, 0xd3, 0x6d, 0xe2, 0x2a, 0x88, 0xda,
0xb0, 0x97, 0x08, 0xba, 0x0a, 0x25, 0x69, 0xef, 0xeb, 0x67, 0x23, 0x5f, 0xf6, 0x7f, 0x71, 0xc1,
0x9b, 0x12, 0x11, 0x53, 0x16, 0x4a, 0x2e, 0x5e, 0x13, 0xb1, 0x22, 0x02, 0x11, 0x38, 0xde, 0x3e,
0x96, 0xd0, 0xf3, 0xbc, 0x56, 0xef, 0x1d, 0x5b, 0x27, 0xcf, 0xde, 0x33, 0x09, 0xca, 0x96, 0xa0,
0xd0, 0xbe, 0x6f, 0xce, 0xa0, 0x17, 0x79, 0x84, 0x07, 0x26, 0xd1, 0x7f, 0xb4, 0xba, 0xde, 0x7c,
0x58, 0x4e, 0xb7, 0x3f, 0x45, 0x36, 0xe8, 0xd9, 0x3d, 0xac, 0x8d, 0x86, 0xe1, 0x70, 0xa3, 0xcd,
0xd1, 0xc7, 0xdb, 0x9b, 0xb8, 0x38, 0xe6, 0xf9, 0xbd, 0xbc, 0x89, 0xf9, 0x63, 0x5d, 0xff, 0x1b,
0xfa, 0xf2, 0x9f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x0b, 0xdf, 0x60, 0xc4, 0x40, 0x09, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
@ -766,7 +889,8 @@ const _ = grpc.SupportPackageIsVersion4
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type TerminatorServerClient interface {
CloseRecommendations(ctx context.Context, in *CloseRecommendationsRequest, opts ...grpc.CallOption) (*CloseRecommendationsResponse, error)
OutlierRecommendations(ctx context.Context, in *OutlierRecommendationsRequest, opts ...grpc.CallOption) (*CloseRecommendationsResponse, error)
ThresholdRecommendations(ctx context.Context, in *ThresholdRecommendationsRequest, opts ...grpc.CallOption) (*CloseRecommendationsResponse, error)
RevenueReport(ctx context.Context, in *RevenueReportRequest, opts ...grpc.CallOption) (*RevenueReportResponse, error)
ChannelInsights(ctx context.Context, in *ChannelInsightsRequest, opts ...grpc.CallOption) (*ChannelInsightsResponse, error)
}
@ -779,9 +903,18 @@ func NewTerminatorServerClient(cc *grpc.ClientConn) TerminatorServerClient {
return &terminatorServerClient{cc}
}
func (c *terminatorServerClient) CloseRecommendations(ctx context.Context, in *CloseRecommendationsRequest, opts ...grpc.CallOption) (*CloseRecommendationsResponse, error) {
func (c *terminatorServerClient) OutlierRecommendations(ctx context.Context, in *OutlierRecommendationsRequest, opts ...grpc.CallOption) (*CloseRecommendationsResponse, error) {
out := new(CloseRecommendationsResponse)
err := c.cc.Invoke(ctx, "/trmrpc.TerminatorServer/CloseRecommendations", in, out, opts...)
err := c.cc.Invoke(ctx, "/trmrpc.TerminatorServer/OutlierRecommendations", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *terminatorServerClient) ThresholdRecommendations(ctx context.Context, in *ThresholdRecommendationsRequest, opts ...grpc.CallOption) (*CloseRecommendationsResponse, error) {
out := new(CloseRecommendationsResponse)
err := c.cc.Invoke(ctx, "/trmrpc.TerminatorServer/ThresholdRecommendations", in, out, opts...)
if err != nil {
return nil, err
}
@ -808,7 +941,8 @@ func (c *terminatorServerClient) ChannelInsights(ctx context.Context, in *Channe
// TerminatorServerServer is the server API for TerminatorServer service.
type TerminatorServerServer interface {
CloseRecommendations(context.Context, *CloseRecommendationsRequest) (*CloseRecommendationsResponse, error)
OutlierRecommendations(context.Context, *OutlierRecommendationsRequest) (*CloseRecommendationsResponse, error)
ThresholdRecommendations(context.Context, *ThresholdRecommendationsRequest) (*CloseRecommendationsResponse, error)
RevenueReport(context.Context, *RevenueReportRequest) (*RevenueReportResponse, error)
ChannelInsights(context.Context, *ChannelInsightsRequest) (*ChannelInsightsResponse, error)
}
@ -817,20 +951,38 @@ func RegisterTerminatorServerServer(s *grpc.Server, srv TerminatorServerServer)
s.RegisterService(&_TerminatorServer_serviceDesc, srv)
}
func _TerminatorServer_CloseRecommendations_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CloseRecommendationsRequest)
func _TerminatorServer_OutlierRecommendations_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(OutlierRecommendationsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TerminatorServerServer).CloseRecommendations(ctx, in)
return srv.(TerminatorServerServer).OutlierRecommendations(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/trmrpc.TerminatorServer/CloseRecommendations",
FullMethod: "/trmrpc.TerminatorServer/OutlierRecommendations",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TerminatorServerServer).CloseRecommendations(ctx, req.(*CloseRecommendationsRequest))
return srv.(TerminatorServerServer).OutlierRecommendations(ctx, req.(*OutlierRecommendationsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _TerminatorServer_ThresholdRecommendations_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ThresholdRecommendationsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TerminatorServerServer).ThresholdRecommendations(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/trmrpc.TerminatorServer/ThresholdRecommendations",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TerminatorServerServer).ThresholdRecommendations(ctx, req.(*ThresholdRecommendationsRequest))
}
return interceptor(ctx, in, info, handler)
}
@ -876,8 +1028,12 @@ var _TerminatorServer_serviceDesc = grpc.ServiceDesc{
HandlerType: (*TerminatorServerServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "CloseRecommendations",
Handler: _TerminatorServer_CloseRecommendations_Handler,
MethodName: "OutlierRecommendations",
Handler: _TerminatorServer_OutlierRecommendations_Handler,
},
{
MethodName: "ThresholdRecommendations",
Handler: _TerminatorServer_ThresholdRecommendations_Handler,
},
{
MethodName: "RevenueReport",

View file

@ -5,12 +5,13 @@ import "google/api/annotations.proto";
package trmrpc;
service TerminatorServer {
rpc CloseRecommendations (CloseRecommendationsRequest) returns (CloseRecommendationsResponse);
rpc OutlierRecommendations (OutlierRecommendationsRequest) returns (CloseRecommendationsResponse);
rpc ThresholdRecommendations (ThresholdRecommendationsRequest) returns (CloseRecommendationsResponse);
rpc RevenueReport (RevenueReportRequest) returns (RevenueReportResponse);
rpc ChannelInsights (ChannelInsightsRequest) returns (ChannelInsightsResponse);
}
message CloseRecommendationsRequest {
message CloseRecommendationRequest {
/*
The minimum amount of time in seconds that a channel should have been
monitored by lnd to be eligible for close. This value is in place to
@ -18,6 +19,31 @@ message CloseRecommendationsRequest {
*/
int64 minimum_monitored = 1;
enum Metric{
UNKNOWN = 0;
UPTIME = 1;
REVENUE = 2;
INCOMING_VOLUME = 3;
OUTGOING_VOLUME = 4;
TOTAL_VOLUME = 5;
}
/*
The data point base close recommendations on. Available options are:
Uptime: ratio of channel peer's uptime to the period they have been
monitored to.
Revenue: the revenue that the channel has produced per block that its
funding transaction has been confirmed for.
*/
Metric metric = 2;
}
message OutlierRecommendationsRequest {
/*
The parameters that are common to all close recommendations.
*/
CloseRecommendationRequest rec_request = 1;
/*
The number of inter-quartile ranges a value needs to be beneath the lower
quartile/ above the upper quartile to be considered a lower/upper outlier.
@ -26,21 +52,43 @@ message CloseRecommendationsRequest {
aggressive recommendations and 3 for conservative recommendations.
*/
float outlier_multiplier = 2;
}
message ThresholdRecommendationsRequest {
/*
The parameters that are common to all close recommendations.
*/
CloseRecommendationRequest rec_request = 1;
/*
Threshold contains the threshold value that is used to recommend channels
for closure.
*/
oneof threshold{
/*
The threshold percentage uptime over observed lifetime beneath which
channels will be recommended for closure.
*/
float uptime_threshold = 3;
}
}
The threshold that recommendations will be calculated based on.
For uptime: ratio of uptime to observed lifetime beneath which channels
will be recommended for closure.
message CloseRecommendationsResponse{
For revenue: revenue per block that capital has been committed to the
channel beneath which channels will be recommended for closure. This
value is provided per block so that channels that have been open for
different periods of time can be compared.
For incoming volume: The incoming volume per block that capital has
been committed to the channel beneath which channels will be recommended
for closure. This value is provided per block so that channels that have
been open for different periods of time can be compared.
For outgoing volume: The outgoing volume per block that capital has been
committed to the channel beneath which channels will be recommended for
closure. This value is provided per block so that channels that have been
open for different periods of time can be compared.
For total volume: The total volume per block that capital has been
committed to the channel beneath which channels will be recommended for
closure. This value is provided per block so that channels that have been
open for different periods of time can be compared.
*/
float threshold_value = 2;
}
message CloseRecommendationsResponse {
/*
The total number of channels, before filtering out channels that are
not eligible for close recommendations.
@ -53,25 +101,15 @@ message CloseRecommendationsResponse{
int32 considered_channels = 2;
/*
A map of channels to close recommendations, based out whether they are
outliers in the uptime dataset. The absence of a channel in this set
implies that it was not considered for close because it did not meet
the criteria for close (it is private, or has not been monitored for
long enough to make a decision).
A set of channel close recommendations. The absence of a channel in this
set implies that it was not considered for close because it did not meet
the criteria for close recommendations (it is private, or has not been
monitored for long enough).
*/
repeated Recommendation outlier_recommendations = 3;
/*
A set of channel close recommendations, based out whether they are
beneath the threshold provided in the request. The absence of a channel
in this set implies that it was not considered for close because it
did not meet the criteria for close (it is private, or has not been
monitored for long enough to make a decision).
*/
repeated Recommendation threshold_recommendations = 4;
repeated Recommendation recommendations = 3;
}
message Recommendation{
message Recommendation {
/*
The channel point [funding txid: outpoint] of the channel being considered
for close.
@ -115,7 +153,7 @@ message RevenueReportResponse {
repeated RevenueReport reports = 1;
}
message RevenueReport{
message RevenueReport {
/*
Target channel is the channel that the report is generated for; incoming
fields in the report mean that this channel was the incoming channel,

View file

@ -129,20 +129,37 @@ func (s *RPCServer) Stop() error {
return nil
}
// CloseRecommendations provides a set of close recommendations for the
// current set of open channels.
func (s *RPCServer) CloseRecommendations(ctx context.Context,
req *CloseRecommendationsRequest) (*CloseRecommendationsResponse,
// OutlierRecommendations provides a set of close recommendations for the
// current set of open channels based on whether they are outliers.
func (s *RPCServer) OutlierRecommendations(ctx context.Context,
req *OutlierRecommendationsRequest) (*CloseRecommendationsResponse,
error) {
cfg := parseRequest(ctx, s.cfg, req)
cfg, multiplier := parseOutlierRequest(ctx, s.cfg, req)
report, err := recommend.CloseRecommendations(cfg)
report, err := recommend.OutlierRecommendations(cfg, multiplier)
if err != nil {
return nil, err
}
return parseResponse(report), nil
return rpcResponse(report), nil
}
// ThresholdRecommendations provides a set of close recommendations for the
// current set of open channels based on whether they are above or below a
// given threshold.
func (s *RPCServer) ThresholdRecommendations(ctx context.Context,
req *ThresholdRecommendationsRequest) (*CloseRecommendationsResponse,
error) {
cfg, threshold := parseThresholdRequest(ctx, s.cfg, req)
report, err := recommend.ThresholdRecommendations(cfg, threshold)
if err != nil {
return nil, err
}
return rpcResponse(report), nil
}
// RevenueReport returns a pairwise revenue report for a channel