mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2026-02-23 15:14:49 +01:00
Histograms: Add Lightning data and API endpoints (#6217)
* Histograms: Add Lightning data and API endpoints Ported over from the mobile-working-branch. Adds histogram data for Lightning and exposes the wallet/lightning histogram data via the API. It also add a dashboard graph for the Lightning balance. Caveat: The Lightning histogram is calculated by using the current channel balance and going backwards through as much invoices and transactions as we have. The "start" of the LN graph data might not be accurate though. That's because we don't track (and not even have) the LN onchain data. It is calculated by using the current channel balance and going backwards through as much invoices and transactions as we have. So the historic graph data for LN is basically a best effort of trying to reconstruct it with what we have: The LN channel transactions. * More timeframes * Refactoring: Remove redundant WalletHistogram types * Remove store property from dashboard tile view models * JS error fixes
This commit is contained in:
@@ -1,13 +1,10 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
@@ -31,8 +28,9 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
PoliciesSettings policiesSettings, LightningClientFactoryService lightningClientFactory,
|
||||
IOptions<LightningNetworkOptions> lightningNetworkOptions,
|
||||
IAuthorizationService authorizationService,
|
||||
PaymentMethodHandlerDictionary handlers
|
||||
) : base(policiesSettings, authorizationService, handlers)
|
||||
PaymentMethodHandlerDictionary handlers,
|
||||
LightningHistogramService lnHistogramService
|
||||
) : base(policiesSettings, authorizationService, handlers, lnHistogramService)
|
||||
{
|
||||
_lightningClientFactory = lightningClientFactory;
|
||||
_lightningNetworkOptions = lightningNetworkOptions;
|
||||
@@ -55,6 +53,14 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
return base.GetBalance(cryptoCode, cancellationToken);
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanUseInternalLightningNode,
|
||||
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/server/lightning/{cryptoCode}/histogram")]
|
||||
public override Task<IActionResult> GetHistogram(string cryptoCode, [FromQuery] HistogramType? type = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return base.GetHistogram(cryptoCode, type, cancellationToken);
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanUseInternalLightningNode,
|
||||
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpPost("~/api/v1/server/lightning/{cryptoCode}/connect")]
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
@@ -34,7 +32,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
IOptions<LightningNetworkOptions> lightningNetworkOptions,
|
||||
LightningClientFactoryService lightningClientFactory, PaymentMethodHandlerDictionary handlers,
|
||||
PoliciesSettings policiesSettings,
|
||||
IAuthorizationService authorizationService) : base(policiesSettings, authorizationService, handlers)
|
||||
IAuthorizationService authorizationService,
|
||||
LightningHistogramService lnHistogramService) : base(policiesSettings, authorizationService, handlers, lnHistogramService)
|
||||
{
|
||||
_lightningNetworkOptions = lightningNetworkOptions;
|
||||
_lightningClientFactory = lightningClientFactory;
|
||||
@@ -56,6 +55,13 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
{
|
||||
return base.GetBalance(cryptoCode, cancellationToken);
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanUseLightningNodeInStore, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/lightning/{cryptoCode}/histogram")]
|
||||
public override Task<IActionResult> GetHistogram(string cryptoCode, [FromQuery] HistogramType? type = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return base.GetHistogram(cryptoCode, type, cancellationToken);
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanUseLightningNodeInStore,
|
||||
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
@@ -64,6 +70,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
{
|
||||
return base.ConnectToNode(cryptoCode, request, cancellationToken);
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanUseLightningNodeInStore,
|
||||
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/lightning/{cryptoCode}/channels")]
|
||||
@@ -71,6 +78,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
{
|
||||
return base.GetChannels(cryptoCode, cancellationToken);
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanUseLightningNodeInStore,
|
||||
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpPost("~/api/v1/stores/{storeId}/lightning/{cryptoCode}/channels")]
|
||||
|
||||
@@ -11,6 +11,7 @@ using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
@@ -32,15 +33,18 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
private readonly PoliciesSettings _policiesSettings;
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
private readonly LightningHistogramService _lnHistogramService;
|
||||
|
||||
protected GreenfieldLightningNodeApiController(
|
||||
PoliciesSettings policiesSettings,
|
||||
IAuthorizationService authorizationService,
|
||||
PaymentMethodHandlerDictionary handlers)
|
||||
PaymentMethodHandlerDictionary handlers,
|
||||
LightningHistogramService lnHistogramService)
|
||||
{
|
||||
_policiesSettings = policiesSettings;
|
||||
_authorizationService = authorizationService;
|
||||
_handlers = handlers;
|
||||
_lnHistogramService = lnHistogramService;
|
||||
}
|
||||
|
||||
public virtual async Task<IActionResult> GetInfo(string cryptoCode, CancellationToken cancellationToken = default)
|
||||
@@ -86,6 +90,22 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
: null
|
||||
});
|
||||
}
|
||||
|
||||
public virtual async Task<IActionResult> GetHistogram(string cryptoCode, HistogramType? type = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Enum.TryParse<HistogramType>(type.ToString(), true, out var histType);
|
||||
var lightningClient = await GetLightningClient(cryptoCode, true);
|
||||
var data = await _lnHistogramService.GetHistogram(lightningClient, histType, cancellationToken);
|
||||
if (data == null) return this.CreateAPIError(404, "histogram-not-found", "The lightning histogram was not found.");
|
||||
|
||||
return Ok(new HistogramData
|
||||
{
|
||||
Type = data.Type,
|
||||
Balance = data.Balance,
|
||||
Series = data.Series,
|
||||
Labels = data.Labels
|
||||
});
|
||||
}
|
||||
|
||||
public virtual async Task<IActionResult> ConnectToNode(string cryptoCode, ConnectToNodeRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -20,7 +19,6 @@ using BTCPayServer.Payments.PayJoin;
|
||||
using BTCPayServer.Payments.PayJoin.Sender;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Labels;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
@@ -58,6 +56,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
private readonly IFeeProviderFactory _feeProviderFactory;
|
||||
private readonly UTXOLocker _utxoLocker;
|
||||
private readonly TransactionLinkProviders _transactionLinkProviders;
|
||||
private readonly WalletHistogramService _walletHistogramService;
|
||||
|
||||
public GreenfieldStoreOnChainWalletsController(
|
||||
IAuthorizationService authorizationService,
|
||||
@@ -74,6 +73,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
WalletReceiveService walletReceiveService,
|
||||
IFeeProviderFactory feeProviderFactory,
|
||||
UTXOLocker utxoLocker,
|
||||
WalletHistogramService walletHistogramService,
|
||||
TransactionLinkProviders transactionLinkProviders
|
||||
)
|
||||
{
|
||||
@@ -91,6 +91,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
_walletReceiveService = walletReceiveService;
|
||||
_feeProviderFactory = feeProviderFactory;
|
||||
_utxoLocker = utxoLocker;
|
||||
_walletHistogramService = walletHistogramService;
|
||||
_transactionLinkProviders = transactionLinkProviders;
|
||||
}
|
||||
|
||||
@@ -114,6 +115,27 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
});
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/wallet/histogram")]
|
||||
public async Task<IActionResult> GetOnChainWalletHistogram(string storeId, string paymentMethodId, [FromQuery] string? type = null)
|
||||
{
|
||||
if (IsInvalidWalletRequest(paymentMethodId, out var network, out var derivationScheme, out var actionResult))
|
||||
return actionResult;
|
||||
|
||||
var walletId = new WalletId(storeId, network.CryptoCode);
|
||||
Enum.TryParse<HistogramType>(type, true, out var histType);
|
||||
var data = await _walletHistogramService.GetHistogram(Store, walletId, histType);
|
||||
if (data == null) return this.CreateAPIError(404, "histogram-not-found", "The wallet histogram was not found.");
|
||||
|
||||
return Ok(new HistogramData
|
||||
{
|
||||
Type = data.Type,
|
||||
Balance = data.Balance,
|
||||
Series = data.Series,
|
||||
Labels = data.Labels
|
||||
});
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/wallet/feerate")]
|
||||
public async Task<IActionResult> GetOnChainFeeRate(string storeId, string paymentMethodId, int? blockTarget = null)
|
||||
|
||||
@@ -14,18 +14,15 @@ using BTCPayServer.Controllers.GreenField;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Security.Greenfield;
|
||||
using BTCPayServer.Services.Apps;
|
||||
using BTCPayServer.Services.Mails;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.StaticFiles;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NBitcoin;
|
||||
using NBXplorer.Models;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using InvoiceData = BTCPayServer.Client.Models.InvoiceData;
|
||||
using Language = BTCPayServer.Client.Models.Language;
|
||||
@@ -385,6 +382,13 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
await GetController<GreenfieldStoreLightningNodeApiController>().GetBalance(cryptoCode, token));
|
||||
}
|
||||
|
||||
public override async Task<HistogramData> GetLightningNodeHistogram(string storeId, string cryptoCode, HistogramType? type = null,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult<HistogramData>(
|
||||
await GetController<GreenfieldStoreLightningNodeApiController>().GetHistogram(cryptoCode, type, token));
|
||||
}
|
||||
|
||||
public override async Task ConnectToLightningNode(string storeId, string cryptoCode,
|
||||
ConnectToNodeRequest request, CancellationToken token = default)
|
||||
{
|
||||
@@ -461,6 +465,13 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
await GetController<GreenfieldInternalLightningNodeApiController>().GetBalance(cryptoCode));
|
||||
}
|
||||
|
||||
public override async Task<HistogramData> GetLightningNodeHistogram(string cryptoCode, HistogramType? type = null,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult<HistogramData>(
|
||||
await GetController<GreenfieldInternalLightningNodeApiController>().GetHistogram(cryptoCode, type, token));
|
||||
}
|
||||
|
||||
public override async Task ConnectToLightningNode(string cryptoCode, ConnectToNodeRequest request,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
@@ -702,6 +713,12 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
return GetFromActionResult<OnChainWalletOverviewData>(
|
||||
await GetController<GreenfieldStoreOnChainWalletsController>().ShowOnChainWalletOverview(storeId, cryptoCode));
|
||||
}
|
||||
|
||||
public override async Task<HistogramData> GetOnChainWalletHistogram(string storeId, string cryptoCode, HistogramType? type = null, CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult<HistogramData>(
|
||||
await GetController<GreenfieldStoreOnChainWalletsController>().GetOnChainWalletHistogram(storeId, cryptoCode, type?.ToString()));
|
||||
}
|
||||
|
||||
public override async Task<OnChainWalletAddressData> GetOnChainWalletReceiveAddress(string storeId,
|
||||
string cryptoCode, bool forceGenerate = false,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
@@ -78,8 +77,7 @@ public partial class UIStoresController
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
|
||||
var vm = new StoreLightningBalanceViewModel { Store = store, CryptoCode = cryptoCode };
|
||||
return ViewComponent("StoreLightningBalance", new { vm });
|
||||
return ViewComponent("StoreLightningBalance", new { Store = store, CryptoCode = cryptoCode });
|
||||
}
|
||||
|
||||
[HttpGet("{storeId}/dashboard/{cryptoCode}/numbers")]
|
||||
|
||||
@@ -6,15 +6,20 @@ using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Components.StoreLightningBalance;
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Security;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using StoreData = BTCPayServer.Data.StoreData;
|
||||
|
||||
namespace BTCPayServer.Controllers;
|
||||
|
||||
@@ -82,6 +87,32 @@ public partial class UIStoresController
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
[HttpGet("{storeId}/lightning/{cryptoCode}/dashboard/balance")]
|
||||
public IActionResult LightningBalanceDashboard(string storeId, string cryptoCode)
|
||||
{
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
|
||||
return ViewComponent("StoreLightningBalance", new { Store = store, CryptoCode = cryptoCode });
|
||||
}
|
||||
|
||||
[HttpGet("{storeId}/lightning/{cryptoCode}/dashboard/balance/{type}")]
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
public async Task<IActionResult> LightningBalanceDashboard(string storeId, string cryptoCode, HistogramType type)
|
||||
{
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
var lightningClient = await GetLightningClient(store, cryptoCode);
|
||||
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
||||
var data = await _lnHistogramService.GetHistogram(lightningClient, type, cts.Token);
|
||||
if (data == null) return NotFound();
|
||||
|
||||
return Json(data);
|
||||
}
|
||||
|
||||
[HttpGet("{storeId}/lightning/{cryptoCode}/setup")]
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
public IActionResult SetupLightningNode(string storeId, string cryptoCode)
|
||||
@@ -321,4 +352,26 @@ public partial class UIStoresController
|
||||
{
|
||||
return store.GetPaymentMethodConfig<T>(paymentMethodId, _handlers);
|
||||
}
|
||||
|
||||
private async Task<ILightningClient?> GetLightningClient(StoreData store, string cryptoCode)
|
||||
{
|
||||
var network = _networkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||
var id = PaymentTypes.LN.GetPaymentMethodId(cryptoCode);
|
||||
var existing = store.GetPaymentMethodConfig<LightningPaymentMethodConfig>(id, _handlers);
|
||||
if (existing == null)
|
||||
return null;
|
||||
|
||||
if (existing.GetExternalLightningUrl() is { } connectionString)
|
||||
{
|
||||
return _lightningClientFactory.Create(connectionString, network);
|
||||
}
|
||||
if (existing.IsInternalNode && _lightningNetworkOptions.InternalLightningByCryptoCode.TryGetValue(cryptoCode, out var internalLightningNode))
|
||||
{
|
||||
var result = await _authorizationService.AuthorizeAsync(HttpContext.User, null,
|
||||
new PolicyRequirement(Policies.CanUseInternalLightningNode));
|
||||
return result.Succeeded ? internalLightningNode : null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
@@ -64,7 +63,9 @@ public partial class UIStoresController : Controller
|
||||
SettingsRepository settingsRepository,
|
||||
CurrencyNameTable currencyNameTable,
|
||||
IStringLocalizer stringLocalizer,
|
||||
EventAggregator eventAggregator)
|
||||
EventAggregator eventAggregator,
|
||||
LightningHistogramService lnHistogramService,
|
||||
LightningClientFactoryService lightningClientFactory)
|
||||
{
|
||||
_rateFactory = rateFactory;
|
||||
_storeRepo = storeRepo;
|
||||
@@ -95,6 +96,8 @@ public partial class UIStoresController : Controller
|
||||
_dataProtector = dataProtector.CreateProtector("ConfigProtector");
|
||||
_webhookNotificationManager = webhookNotificationManager;
|
||||
_lightningNetworkOptions = lightningNetworkOptions.Value;
|
||||
_lnHistogramService = lnHistogramService;
|
||||
_lightningClientFactory = lightningClientFactory;
|
||||
StringLocalizer = stringLocalizer;
|
||||
}
|
||||
|
||||
@@ -127,6 +130,8 @@ public partial class UIStoresController : Controller
|
||||
private readonly WebhookSender _webhookNotificationManager;
|
||||
private readonly LightningNetworkOptions _lightningNetworkOptions;
|
||||
private readonly IDataProtector _dataProtector;
|
||||
private readonly LightningHistogramService _lnHistogramService;
|
||||
private readonly LightningClientFactoryService _lightningClientFactory;
|
||||
|
||||
public string? GeneratedPairingCode { get; set; }
|
||||
public IStringLocalizer StringLocalizer { get; }
|
||||
|
||||
@@ -307,14 +307,13 @@ namespace BTCPayServer.Controllers
|
||||
[HttpGet("{walletId}/histogram/{type}")]
|
||||
public async Task<IActionResult> WalletHistogram(
|
||||
[ModelBinder(typeof(WalletIdModelBinder))]
|
||||
WalletId walletId, WalletHistogramType type)
|
||||
WalletId walletId, HistogramType type)
|
||||
{
|
||||
var store = GetCurrentStore();
|
||||
var data = await _walletHistogramService.GetHistogram(store, walletId, type);
|
||||
if (data == null) return NotFound();
|
||||
|
||||
return data == null
|
||||
? NotFound()
|
||||
: Json(data);
|
||||
return Json(data);
|
||||
}
|
||||
|
||||
[HttpGet("{walletId}/receive")]
|
||||
|
||||
Reference in New Issue
Block a user