mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2024-11-19 01:43:50 +01:00
Show Lightning node availability in navigation (#5951)
* Show Lightning node availability in navigation Instead of simply communicating the setup state of the store's LN node, this now also checks its availability. Closes #5940. * Cleanups * Add Selenium test for public node page and status in nav * Cache the available lightning node result --------- Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
This commit is contained in:
parent
d3277306cf
commit
8d429f064b
@ -6,6 +6,7 @@ using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
@ -1965,6 +1966,60 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.First());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Selenium", "Selenium")]
|
||||
[Trait("Lightning", "Lightning")]
|
||||
public async Task CanManageLightningNode()
|
||||
{
|
||||
using var s = CreateSeleniumTester();
|
||||
s.Server.ActivateLightning();
|
||||
await s.StartAsync();
|
||||
await s.Server.EnsureChannelsSetup();
|
||||
s.RegisterNewUser(true);
|
||||
(string storeName, _) = s.CreateNewStore();
|
||||
|
||||
// Check status in navigation
|
||||
s.Driver.FindElement(By.CssSelector("#StoreNav-LightningBTC .btcpay-status--pending"));
|
||||
|
||||
// Set up LN node
|
||||
s.AddLightningNode();
|
||||
s.Driver.FindElement(By.CssSelector("#StoreNav-LightningBTC .btcpay-status--enabled"));
|
||||
|
||||
// Check public node info for availability
|
||||
s.Driver.FindElement(By.Id("PublicNodeInfo")).Click();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||
Assert.Equal(storeName, s.Driver.FindElement(By.CssSelector(".store-name")).Text);
|
||||
Assert.Equal("BTC Lightning Node", s.Driver.FindElement(By.Id("LightningNodeTitle")).Text);
|
||||
Assert.Equal("Online", s.Driver.FindElement(By.Id("LightningNodeStatus")).Text);
|
||||
s.Driver.FindElement(By.CssSelector(".btcpay-status--enabled"));
|
||||
s.Driver.FindElement(By.Id("LightningNodeUrlClearnet"));
|
||||
s.Driver.Close();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.First());
|
||||
|
||||
// Set wrong node connection string to simulate offline node
|
||||
s.GoToLightningSettings();
|
||||
s.Driver.FindElement(By.Id("SetupLightningNodeLink")).Click();
|
||||
s.Driver.FindElement(By.CssSelector("label[for=\"LightningNodeType-Custom\"]")).Click();
|
||||
s.Driver.WaitForElement(By.Id("ConnectionString")).Clear();
|
||||
s.Driver.FindElement(By.Id("ConnectionString")).SendKeys("type=lnd-rest;server=https://doesnotwork:8080/");
|
||||
s.Driver.FindElement(By.Id("test")).Click();
|
||||
Assert.Contains("Error", s.FindAlertMessage(StatusMessageModel.StatusSeverity.Error).Text);
|
||||
s.Driver.FindElement(By.Id("save")).Click();
|
||||
Assert.Contains("BTC Lightning node updated.", s.FindAlertMessage().Text);
|
||||
|
||||
// Check offline state is communicated in nav item
|
||||
s.Driver.FindElement(By.CssSelector("#StoreNav-LightningBTC .btcpay-status--disabled"));
|
||||
|
||||
// Check public node info for availability
|
||||
s.Driver.FindElement(By.Id("PublicNodeInfo")).Click();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||
Assert.Equal(storeName, s.Driver.FindElement(By.CssSelector(".store-name")).Text);
|
||||
Assert.Equal("BTC Lightning Node", s.Driver.FindElement(By.Id("LightningNodeTitle")).Text);
|
||||
Assert.Equal("Unavailable", s.Driver.FindElement(By.Id("LightningNodeStatus")).Text);
|
||||
s.Driver.FindElement(By.CssSelector(".btcpay-status--disabled"));
|
||||
s.Driver.AssertElementNotFound(By.Id("LightningNodeUrlClearnet"));
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
public async Task CanImportWallet()
|
||||
{
|
||||
@ -2680,7 +2735,7 @@ namespace BTCPayServer.Tests
|
||||
items = cartData.FindElements(By.CssSelector("tbody tr"));
|
||||
sums = cartData.FindElements(By.CssSelector("tfoot tr"));
|
||||
Assert.Equal(3, items.Count);
|
||||
Assert.Equal(1, sums.Count);
|
||||
Assert.Single(sums);
|
||||
Assert.Contains("Black Tea", items[0].FindElement(By.CssSelector("th")).Text);
|
||||
Assert.Contains("1 x 1,00 € = 1,00 €", items[0].FindElement(By.CssSelector("td")).Text);
|
||||
Assert.Contains("Green Tea", items[1].FindElement(By.CssSelector("th")).Text);
|
||||
|
@ -79,8 +79,11 @@
|
||||
<li class="nav-item">
|
||||
@if (isSetUp)
|
||||
{
|
||||
var status = scheme.Enabled
|
||||
? scheme.Available ? "enabled" : "disabled"
|
||||
: "pending";
|
||||
<a asp-area="" asp-controller="UIStores" asp-action="Lightning" asp-route-cryptoCode="@scheme.CryptoCode" asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.IsActivePage(StoreNavPages.Lightning) @ViewData.IsActivePage(StoreNavPages.LightningSettings)" id="@($"StoreNav-Lightning{scheme.CryptoCode}")">
|
||||
<span class="me-2 btcpay-status btcpay-status--@(scheme.Enabled ? "enabled" : "pending")"></span>
|
||||
<span class="me-2 btcpay-status btcpay-status--@status"></span>
|
||||
<span>@PrettyName.PrettyName(scheme.PaymentMethodId)</span>
|
||||
</a>
|
||||
}
|
||||
|
@ -1,9 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
@ -14,6 +16,7 @@ using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using NBitcoin;
|
||||
using NBitcoin.Secp256k1;
|
||||
|
||||
@ -28,6 +31,8 @@ namespace BTCPayServer.Components.MainNav
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly PaymentMethodHandlerDictionary _paymentMethodHandlerDictionary;
|
||||
private readonly SettingsRepository _settingsRepository;
|
||||
private readonly IMemoryCache _cache;
|
||||
|
||||
public PoliciesSettings PoliciesSettings { get; }
|
||||
|
||||
public MainNav(
|
||||
@ -38,6 +43,7 @@ namespace BTCPayServer.Components.MainNav
|
||||
UserManager<ApplicationUser> userManager,
|
||||
PaymentMethodHandlerDictionary paymentMethodHandlerDictionary,
|
||||
SettingsRepository settingsRepository,
|
||||
IMemoryCache cache,
|
||||
PoliciesSettings policiesSettings)
|
||||
{
|
||||
_storeRepo = storeRepo;
|
||||
@ -47,6 +53,7 @@ namespace BTCPayServer.Components.MainNav
|
||||
_storesController = storesController;
|
||||
_paymentMethodHandlerDictionary = paymentMethodHandlerDictionary;
|
||||
_settingsRepository = settingsRepository;
|
||||
_cache = cache;
|
||||
PoliciesSettings = policiesSettings;
|
||||
}
|
||||
|
||||
@ -69,6 +76,38 @@ namespace BTCPayServer.Components.MainNav
|
||||
// Wallets
|
||||
_storesController.AddPaymentMethods(store, storeBlob,
|
||||
out var derivationSchemes, out var lightningNodes);
|
||||
|
||||
foreach (var lnNode in lightningNodes)
|
||||
{
|
||||
var pmi = PaymentTypes.LN.GetPaymentMethodId(lnNode.CryptoCode);
|
||||
if (_paymentMethodHandlerDictionary.TryGet(pmi) is not LightningLikePaymentHandler handler)
|
||||
continue;
|
||||
|
||||
if (lnNode.CacheKey is not null)
|
||||
{
|
||||
using var cts = new CancellationTokenSource(5000);
|
||||
try
|
||||
{
|
||||
lnNode.Available = await _cache.GetOrCreateAsync(lnNode.CacheKey, async entry =>
|
||||
{
|
||||
entry.SetAbsoluteExpiration(TimeSpan.FromMinutes(5));
|
||||
try
|
||||
{
|
||||
var paymentMethodDetails = store.GetPaymentMethodConfig<LightningPaymentMethodConfig>(pmi, _paymentMethodHandlerDictionary);
|
||||
await handler.GetNodeInfo(paymentMethodDetails, null, throws: true);
|
||||
// if we came here without exception, this means the node is available
|
||||
return true;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}).WithCancellation(cts.Token);
|
||||
}
|
||||
catch when (cts.IsCancellationRequested) { }
|
||||
}
|
||||
}
|
||||
|
||||
vm.DerivationSchemes = derivationSchemes;
|
||||
vm.LightningNodes = lightningNodes;
|
||||
|
||||
|
@ -5,7 +5,6 @@ using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Filters;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
@ -55,7 +54,6 @@ namespace BTCPayServer.Controllers
|
||||
try
|
||||
{
|
||||
var paymentMethodDetails = store.GetPaymentMethodConfig<LightningPaymentMethodConfig>(pmi, _handlers);
|
||||
var network = _BtcPayNetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||
var nodeInfo = await handler.GetNodeInfo(paymentMethodDetails, null, throws: true);
|
||||
|
||||
vm.Available = true;
|
||||
|
@ -1,6 +1,9 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Client;
|
||||
@ -15,6 +18,7 @@ using BTCPayServer.Payments.Lightning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
|
||||
namespace BTCPayServer.Controllers;
|
||||
|
||||
@ -161,10 +165,19 @@ public partial class UIStoresController
|
||||
CryptoCode = lnNetwork.CryptoCode,
|
||||
PaymentMethodId = handler.PaymentMethodId,
|
||||
Address = lightning?.GetDisplayableConnectionString(),
|
||||
Enabled = isEnabled
|
||||
});
|
||||
Enabled = isEnabled,
|
||||
CacheKey = GetCacheKey(lightning)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string? GetCacheKey(LightningPaymentMethodConfig? lightning)
|
||||
{
|
||||
if (lightning is null)
|
||||
return null;
|
||||
var connStr = lightning.IsInternalNode ? lightning.InternalNodeRef : lightning.ConnectionString;
|
||||
connStr ??= string.Empty;
|
||||
return "LN-INFO-" + Encoders.Hex.EncodeData(SHA256.HashData(Encoding.UTF8.GetBytes(connStr))[0..4]);
|
||||
}
|
||||
}
|
||||
|
@ -8,5 +8,7 @@ namespace BTCPayServer.Models.StoreViewModels
|
||||
public PaymentMethodId PaymentMethodId { get; set; }
|
||||
public string Address { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
public bool Available { get; set; }
|
||||
public string CacheKey { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,8 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AngleSharp.Dom;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Configuration;
|
||||
@ -13,17 +10,12 @@ using BTCPayServer.Data;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Lightning.LndHub;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.InvoicingModels;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NBitcoin;
|
||||
using NBitcoin.Protocol;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
@ -41,7 +33,6 @@ namespace BTCPayServer.Payments.Lightning
|
||||
private readonly LightningClientFactoryService _lightningClientFactory;
|
||||
private readonly BTCPayNetwork _Network;
|
||||
private readonly SocketFactory _socketFactory;
|
||||
private readonly DisplayFormatter _displayFormatter;
|
||||
private readonly ISettingsAccessor<PoliciesSettings> _policies;
|
||||
private readonly IOptions<LightningNetworkOptions> _lightningNetworkOptions;
|
||||
|
||||
@ -51,7 +42,6 @@ namespace BTCPayServer.Payments.Lightning
|
||||
LightningClientFactoryService lightningClientFactory,
|
||||
BTCPayNetwork network,
|
||||
SocketFactory socketFactory,
|
||||
DisplayFormatter displayFormatter,
|
||||
IOptions<LightningNetworkOptions> options,
|
||||
ISettingsAccessor<PoliciesSettings> policies,
|
||||
IOptions<LightningNetworkOptions> lightningNetworkOptions)
|
||||
@ -61,7 +51,6 @@ namespace BTCPayServer.Payments.Lightning
|
||||
_lightningClientFactory = lightningClientFactory;
|
||||
_Network = network;
|
||||
_socketFactory = socketFactory;
|
||||
_displayFormatter = displayFormatter;
|
||||
Options = options;
|
||||
_policies = policies;
|
||||
_lightningNetworkOptions = lightningNetworkOptions;
|
||||
@ -155,7 +144,7 @@ namespace BTCPayServer.Payments.Lightning
|
||||
var synced = _Dashboard.IsFullySynched(_Network.CryptoCode, out var summary);
|
||||
if (supportedPaymentMethod.IsInternalNode && !synced)
|
||||
throw new PaymentMethodUnavailableException("Full node not available");
|
||||
;
|
||||
|
||||
try
|
||||
{
|
||||
using var cts = new CancellationTokenSource(LightningTimeout);
|
||||
|
@ -1,13 +1,8 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Linq;
|
||||
#if ALTCOINS
|
||||
using BTCPayServer.Services.Altcoins.Monero.Payments;
|
||||
using BTCPayServer.Services.Altcoins.Zcash.Payments;
|
||||
#endif
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Payments
|
||||
{
|
||||
|
@ -18,13 +18,13 @@
|
||||
<div class="d-flex flex-column justify-content-center gap-4">
|
||||
<partial name="_StoreHeader" model="(Model.StoreName, Model.StoreBranding)" />
|
||||
<section class="tile">
|
||||
<h2 class="h4 card-subtitle text-center text-secondary mt-1 mb-3">
|
||||
<h2 class="h4 card-subtitle text-center text-secondary mt-1 mb-3" id="LightningNodeTitle">
|
||||
<span>@Model.CryptoCode</span>
|
||||
Lightning Node
|
||||
</h2>
|
||||
<h4 class="d-flex align-items-center justify-content-center gap-2 my-4">
|
||||
<span class="btcpay-status btcpay-status--@(Model.Available ? "enabled" : "disabled")" style="margin-top:.1rem;"></span>
|
||||
@(Model.Available ? "Online" : "Unavailable")
|
||||
<span id="LightningNodeStatus">@(Model.Available ? "Online" : "Unavailable")</span>
|
||||
</h4>
|
||||
@if (Model.Available)
|
||||
{
|
||||
@ -46,6 +46,7 @@
|
||||
{
|
||||
var nodeInfo = Model.NodeInfo[i];
|
||||
var title = nodeInfo.IsTor ? "Tor" : "Clearnet";
|
||||
var id = $"LightningNodeUrl{title}";
|
||||
var value = nodeInfo.ToString();
|
||||
<div class="tab-pane fade @(i == 0 ? "show active" : "")" id="nodeInfo-@i" role="tabpanel" aria-labelledby="nodeInfo-tab-@i">
|
||||
<div class="payment-box">
|
||||
@ -58,7 +59,7 @@
|
||||
</div>
|
||||
<div class="input-group mt-3">
|
||||
<div class="form-floating">
|
||||
<vc:truncate-center text="@value" padding="15" elastic="true" classes="form-control-plaintext" />
|
||||
<vc:truncate-center text="@value" padding="15" elastic="true" classes="form-control-plaintext" id="@id"/>
|
||||
<label>@title</label>
|
||||
</div>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user