diff --git a/accounting/config.go b/accounting/config.go index 10277eef..972c8d9e 100644 --- a/accounting/config.go +++ b/accounting/config.go @@ -96,6 +96,9 @@ type CommonConfig struct { // Categories is a set of custom categories which should be added to the // report. Categories []CustomCategory + + // SocksProxy is the URL to be used as a proxy for http requests. + SocksProxy string } // NewOnChainConfig returns an on chain config from the lnd services provided. diff --git a/accounting/conversions.go b/accounting/conversions.go index d72c5c2c..79cb7039 100644 --- a/accounting/conversions.go +++ b/accounting/conversions.go @@ -34,7 +34,7 @@ func invertMsat(msat int64) int64 { // individual price points from this data. func getConversion(ctx context.Context, startTime, endTime time.Time, disableFiat bool, fiatBackend fiat.PriceBackend, - granularity *fiat.Granularity) (usdPrice, error) { + granularity *fiat.Granularity, socksProxy string) (usdPrice, error) { // If we don't want fiat values, just return a price which will yield // a zero price and timestamp. @@ -49,7 +49,9 @@ func getConversion(ctx context.Context, startTime, endTime time.Time, return nil, err } - fiatClient, err := fiat.NewPricePriceSource(fiatBackend, granularity) + fiatClient, err := fiat.NewPricePriceSource( + fiatBackend, granularity, socksProxy, + ) if err != nil { return nil, err } diff --git a/accounting/off_chain.go b/accounting/off_chain.go index 557cf212..21892a22 100644 --- a/accounting/off_chain.go +++ b/accounting/off_chain.go @@ -45,7 +45,7 @@ func OffChainReport(ctx context.Context, cfg *OffChainConfig) (Report, error) { // or a no-op function if we do not want prices. getPrice, err := getConversion( ctx, cfg.StartTime, cfg.EndTime, cfg.DisableFiat, - cfg.FiatBackend, cfg.Granularity, + cfg.FiatBackend, cfg.Granularity, cfg.SocksProxy, ) if err != nil { return nil, err diff --git a/accounting/on_chain.go b/accounting/on_chain.go index 9e90ae0b..08ac90c9 100644 --- a/accounting/on_chain.go +++ b/accounting/on_chain.go @@ -21,7 +21,7 @@ func OnChainReport(ctx context.Context, cfg *OnChainConfig) (Report, error) { // or a no-op function if we do not want prices. getPrice, err := getConversion( ctx, cfg.StartTime, cfg.EndTime, cfg.DisableFiat, - cfg.FiatBackend, cfg.Granularity, + cfg.FiatBackend, cfg.Granularity, cfg.SocksProxy, ) if err != nil { return nil, err diff --git a/fiat/coindesk_api.go b/fiat/coindesk_api.go index 37011e0b..4bdcd150 100644 --- a/fiat/coindesk_api.go +++ b/fiat/coindesk_api.go @@ -20,7 +20,9 @@ const ( ) // coinDeskAPI implements the fiatBackend interface. -type coinDeskAPI struct{} +type coinDeskAPI struct { + httpClient *http.Client +} type coinDeskResponse struct { Data map[string]float64 `json:"bpi"` @@ -28,7 +30,7 @@ type coinDeskResponse struct { // queryCoinDesk constructs and sends a request to coindesk to query historical // price information. -func queryCoinDesk(start, end time.Time) ([]byte, error) { +func (c *coinDeskAPI) queryCoinDesk(start, end time.Time) ([]byte, error) { queryURL := fmt.Sprintf("%v?start=%v&end=%v", coinDeskHistoryAPI, start.Format(coinDeskTimeFormat), end.Format(coinDeskTimeFormat)) @@ -36,8 +38,7 @@ func queryCoinDesk(start, end time.Time) ([]byte, error) { log.Debugf("coindesk url: %v", queryURL) // Query the http endpoint with the url provided - // #nosec G107 - response, err := http.Get(queryURL) + response, err := c.httpClient.Get(queryURL) if err != nil { return nil, err } @@ -77,7 +78,7 @@ func (c *coinDeskAPI) rawPriceData(ctx context.Context, start, end time.Time) ([]*USDPrice, error) { query := func() ([]byte, error) { - return queryCoinDesk(start, end) + return c.queryCoinDesk(start, end) } // CoinDesk uses a granularity of 1 day and does not include the current diff --git a/fiat/prices.go b/fiat/prices.go index 1a350196..962a99ff 100644 --- a/fiat/prices.go +++ b/fiat/prices.go @@ -3,6 +3,8 @@ package fiat import ( "context" "errors" + "net/http" + "net/url" "sort" "time" @@ -91,6 +93,11 @@ var priceBackendNames = map[PriceBackend]string{ CoinDeskPriceBackend: "coindesk", } +var priceBackendProxySupport = map[PriceBackend]bool{ + CoinCapPriceBackend: false, + CoinDeskPriceBackend: true, +} + // String returns the string representation of a price backend. func (p PriceBackend) String() string { return priceBackendNames[p] @@ -98,11 +105,45 @@ func (p PriceBackend) String() string { // NewPricePriceSource returns a PriceSource which can be used to query price // data. -func NewPricePriceSource(backend PriceBackend, granularity *Granularity) ( - *PriceSource, error) { +func NewPricePriceSource(backend PriceBackend, granularity *Granularity, + proxy string) (*PriceSource, error) { + + // If no specific backend is specified then choose a default backend + // based on whether or not a proxy is specified. + if backend == UnknownPriceBackend { + if proxy != "" { + backend = CoinDeskPriceBackend + } else { + backend = CoinCapPriceBackend + } + } + + // Check that the proxy flag is not set for a price backend that does + // not support requests through a socks proxy. + if proxy != "" && !priceBackendProxySupport[backend] { + return nil, errors.New("can't use a socks proxy for the " + + "given backend") + } + + httpClient := http.DefaultClient + if proxy != "" { + // Parse socks proxy URL string to a URL type + socksProxyURL, err := url.Parse("socks5://" + proxy) + if err != nil { + return nil, err + } + + // Set up a custom HTTP transport to use the proxy and + // create the client + httpClient = &http.Client{ + Transport: &http.Transport{ + Proxy: http.ProxyURL(socksProxyURL), + }, + } + } switch backend { - case UnknownPriceBackend, CoinCapPriceBackend: + case CoinCapPriceBackend: if granularity == nil { return nil, errGranularityRequired } @@ -112,7 +153,9 @@ func NewPricePriceSource(backend PriceBackend, granularity *Granularity) ( case CoinDeskPriceBackend: return &PriceSource{ - impl: &coinDeskAPI{}, + impl: &coinDeskAPI{ + httpClient: httpClient, + }, }, nil } @@ -133,7 +176,7 @@ type PriceRequest struct { // GetPrices gets a set of prices for a set of timestamps. func GetPrices(ctx context.Context, timestamps []time.Time, - backend PriceBackend, granularity Granularity) ( + backend PriceBackend, granularity Granularity, socksProxy string) ( map[time.Time]*USDPrice, error) { if len(timestamps) == 0 { @@ -152,7 +195,7 @@ func GetPrices(ctx context.Context, timestamps []time.Time, // timestamp if we have 1 entry, but that's ok. start, end := timestamps[0], timestamps[len(timestamps)-1] - client, err := NewPricePriceSource(backend, &granularity) + client, err := NewPricePriceSource(backend, &granularity, socksProxy) if err != nil { return nil, err } diff --git a/frdrpc/rpcserver.go b/frdrpc/rpcserver.go index fbdc3719..fd6a07c9 100644 --- a/frdrpc/rpcserver.go +++ b/frdrpc/rpcserver.go @@ -443,7 +443,7 @@ func (s *RPCServer) ExchangeRate(ctx context.Context, } prices, err := fiat.GetPrices( - ctx, timestamps, fiatBackend, *granularity, + ctx, timestamps, fiatBackend, *granularity, "", ) if err != nil { return nil, err