diff --git a/cmd/trmcli/channel_insights.go b/cmd/trmcli/channel_insights.go index 0350987..9b41b21 100644 --- a/cmd/trmcli/channel_insights.go +++ b/cmd/trmcli/channel_insights.go @@ -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 } diff --git a/cmd/trmcli/close_recommendations.go b/cmd/trmcli/close_recommendations.go index 05bb978..2ca27e8 100644 --- a/cmd/trmcli/close_recommendations.go +++ b/cmd/trmcli/close_recommendations.go @@ -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 } diff --git a/cmd/trmcli/main.go b/cmd/trmcli/main.go index b49b748..59b11f2 100644 --- a/cmd/trmcli/main.go +++ b/cmd/trmcli/main.go @@ -23,7 +23,8 @@ func main() { }, } app.Commands = []cli.Command{ - closeRecommendationCommand, + thresholdRecommendationCommand, + outlierRecommendationCommand, revenueReportCommand, channelInsightsCommand, } diff --git a/recommend/recommend.go b/recommend/recommend.go index df3efdd..a632bbc 100644 --- a/recommend/recommend.go +++ b/recommend/recommend.go @@ -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) +} diff --git a/recommend/recommend_test.go b/recommend/recommend_test.go index 1691628..26fde27 100644 --- a/recommend/recommend_test.go +++ b/recommend/recommend_test.go @@ -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)) } } }) diff --git a/trmrpc/close_recommendations.go b/trmrpc/close_recommendations.go index 3a84c6b..2ec043d 100644 --- a/trmrpc/close_recommendations.go +++ b/trmrpc/close_recommendations.go @@ -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 } diff --git a/trmrpc/rpc.pb.go b/trmrpc/rpc.pb.go index 01b170b..d4b7c8b 100644 --- a/trmrpc/rpc.pb.go +++ b/trmrpc/rpc.pb.go @@ -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", diff --git a/trmrpc/rpc.proto b/trmrpc/rpc.proto index 363f986..c6338fd 100644 --- a/trmrpc/rpc.proto +++ b/trmrpc/rpc.proto @@ -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, diff --git a/trmrpc/rpcserver.go b/trmrpc/rpcserver.go index 545bd8b..a10b044 100644 --- a/trmrpc/rpcserver.go +++ b/trmrpc/rpcserver.go @@ -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