mirror of
https://github.com/lightninglabs/faraday.git
synced 2026-08-13 12:33:35 +02:00
246 lines
7.5 KiB
Go
246 lines
7.5 KiB
Go
// Package frdrpcserver contains the server logic for faraday's grpc server
|
|
// which serves requests for close recommendations.
|
|
//
|
|
// The Faraday server interface is implemented by the RPCServer struct.
|
|
// To keep this file readable, each function implemented by the interface
|
|
// has a file named after the function call which contains rpc parsing
|
|
// code for the request and response. If the call requires extensive
|
|
// additional logic, and unexported function with the same name should
|
|
// be created in this file as well.
|
|
package frdrpcserver
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"time"
|
|
|
|
"github.com/btcsuite/btcd/btcutil"
|
|
"github.com/lightninglabs/faraday/accounting"
|
|
"github.com/lightninglabs/faraday/chain"
|
|
"github.com/lightninglabs/faraday/chanevents"
|
|
"github.com/lightninglabs/faraday/fiat"
|
|
"github.com/lightninglabs/faraday/frdrpc"
|
|
"github.com/lightninglabs/faraday/recommend"
|
|
"github.com/lightninglabs/faraday/resolutions"
|
|
"github.com/lightninglabs/faraday/revenue"
|
|
"github.com/lightninglabs/lndclient"
|
|
)
|
|
|
|
var (
|
|
// maxInvoiceQueries is the maximum number of invoices we request from
|
|
// lnd at a time.
|
|
maxInvoiceQueries = 1000
|
|
|
|
// maxPaymentQueries is the maximum number of invoices we request from
|
|
// lnd at a time. It is less than the number of invoices we request
|
|
// because payments have a lot of htlc data.
|
|
maxPaymentQueries = 500
|
|
|
|
// maxForwardQueries is the maximum number of forwards we request from
|
|
// lnd at a time. It is more than the number of invoices we request
|
|
// because forwards have less data.
|
|
maxForwardQueries = 2000
|
|
|
|
// ErrBitcoinNodeRequired is required when an endpoint which requires
|
|
// a bitcoin node backend is hit and we are not connected to one.
|
|
ErrBitcoinNodeRequired = errors.New("bitcoin node required")
|
|
)
|
|
|
|
// RPCServer implements the faraday service, serving requests over grpc.
|
|
type RPCServer struct {
|
|
// Required by the grpc-gateway/v2 library for forward compatibility.
|
|
frdrpc.UnimplementedFaradayServerServer
|
|
|
|
// cfg contains closures and settings required for operation.
|
|
cfg *Config
|
|
}
|
|
|
|
// ForwardingAnalyzer is the seam the RPC server uses to compute per-peer-pair
|
|
// forwarding facts. It is an interface so the handler can be exercised against
|
|
// a mock analyzer in tests.
|
|
type ForwardingAnalyzer interface {
|
|
EffectiveUptime(ctx context.Context, startTime, endTime time.Time,
|
|
liquidityFloor btcutil.Amount) (
|
|
map[chanevents.PeerPair]chanevents.ForwardingAbility, error)
|
|
}
|
|
|
|
// Config provides closures and settings required to run the rpc server.
|
|
type Config struct {
|
|
// Lnd is a client which can be used to query lnd.
|
|
Lnd lndclient.LndServices
|
|
|
|
// ChanEvents is a database of channel events.
|
|
ChanEvents *chanevents.Store
|
|
|
|
// ForwardingAnalyzer computes forwarding-ability facts for the
|
|
// ForwardingAbility RPC. When nil, that endpoint returns
|
|
// codes.Unavailable.
|
|
ForwardingAnalyzer ForwardingAnalyzer
|
|
|
|
// BitcoinClient is an optional client which can be used to query
|
|
// on-chain data from a connected bitcoin node. If nil, faraday will
|
|
// not be able to serve endpoints which require on-chain data.
|
|
BitcoinClient chain.BitcoinClient
|
|
}
|
|
|
|
// NewRPCServer returns a new RPCServer backed by the given config.
|
|
func NewRPCServer(cfg *Config) *RPCServer {
|
|
return &RPCServer{
|
|
cfg: cfg,
|
|
}
|
|
}
|
|
|
|
// 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 *frdrpc.OutlierRecommendationsRequest) (*frdrpc.CloseRecommendationsResponse,
|
|
error) {
|
|
|
|
if req.RecRequest == nil {
|
|
return nil, errors.New("recommendation request field required")
|
|
}
|
|
|
|
log.Debugf("[OutlierRecommendations]: metric: %v, multiplier: %v",
|
|
req.RecRequest.Metric, req.OutlierMultiplier)
|
|
|
|
cfg, multiplier := parseOutlierRequest(ctx, s.cfg, req)
|
|
|
|
report, err := recommend.OutlierRecommendations(cfg, multiplier)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
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 *frdrpc.ThresholdRecommendationsRequest) (*frdrpc.CloseRecommendationsResponse,
|
|
error) {
|
|
|
|
if req.RecRequest == nil {
|
|
return nil, errors.New("recommendation request field required")
|
|
}
|
|
|
|
log.Debugf("[ThresholdRecommendations]: metric: %v, threshold: %v",
|
|
req.RecRequest.Metric, req.ThresholdValue)
|
|
|
|
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
|
|
// over the period requested.
|
|
func (s *RPCServer) RevenueReport(ctx context.Context,
|
|
req *frdrpc.RevenueReportRequest) (*frdrpc.RevenueReportResponse, error) {
|
|
|
|
log.Debugf("[RevenueReport]: range: %v-%v, channels: %v", req.StartTime,
|
|
req.EndTime, req.ChanPoints)
|
|
|
|
revenueConfig := parseRevenueRequest(ctx, s.cfg, req)
|
|
|
|
report, err := revenue.GetRevenueReport(revenueConfig)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return rpcRevenueResponse(req.GetChanPoints(), report)
|
|
}
|
|
|
|
// ChannelInsights returns the channel insights for our currently open set
|
|
// of channels.
|
|
func (s *RPCServer) ChannelInsights(ctx context.Context,
|
|
_ *frdrpc.ChannelInsightsRequest) (*frdrpc.ChannelInsightsResponse, error) {
|
|
|
|
log.Debugf("[ChannelInsights]")
|
|
|
|
insights, err := channelInsights(ctx, s.cfg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return rpcChannelInsightsResponse(insights), nil
|
|
}
|
|
|
|
// ExchangeRate provides a fiat estimate for a set of timestamped bitcoin
|
|
// prices.
|
|
func (s *RPCServer) ExchangeRate(ctx context.Context,
|
|
req *frdrpc.ExchangeRateRequest) (*frdrpc.ExchangeRateResponse, error) {
|
|
|
|
log.Debugf("[FiatEstimate]: %v requests", len(req.Timestamps))
|
|
|
|
timestamps, priceCfg, err := parseExchangeRateRequest(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
prices, err := fiat.GetPrices(ctx, timestamps, priceCfg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return exchangeRateResponse(prices), nil
|
|
}
|
|
|
|
// NodeAudit returns an on chain report for the period requested.
|
|
func (s *RPCServer) NodeAudit(ctx context.Context,
|
|
req *frdrpc.NodeAuditRequest) (*frdrpc.NodeAuditResponse, error) {
|
|
|
|
log.Debugf("[NodeAudit]: range: %v-%v, fiat: %v", req.StartTime,
|
|
req.EndTime, req.DisableFiat)
|
|
|
|
onChain, offChain, err := parseNodeAuditRequest(ctx, s.cfg, req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
onChainReport, err := accounting.OnChainReport(ctx, onChain)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
offChainReport, err := accounting.OffChainReport(ctx, offChain)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return rpcReportResponse(append(onChainReport, offChainReport...))
|
|
}
|
|
|
|
// CloseReport returns a close report for the channel provided. Note that this
|
|
// endpoint requires connection to an external bitcoind node.
|
|
func (s *RPCServer) CloseReport(ctx context.Context,
|
|
req *frdrpc.CloseReportRequest) (*frdrpc.CloseReportResponse, error) {
|
|
|
|
log.Debugf("[CloseReport]: %v", req.ChannelPoint)
|
|
|
|
if err := s.requireNode(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
cfg := parseCloseReportRequest(ctx, s.cfg)
|
|
|
|
report, err := resolutions.ChannelCloseReport(cfg, req.ChannelPoint)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return rpcCloseReportResponse(report), nil
|
|
}
|
|
|
|
// requireNode fails if we do not have a connection to a backing bitcoin node.
|
|
func (s *RPCServer) requireNode() error {
|
|
if s.cfg.BitcoinClient == nil {
|
|
return ErrBitcoinNodeRequired
|
|
}
|
|
|
|
return nil
|
|
}
|