Support the new LN lib (#5422)

* Support the new LN lib

* fix test

* do not cache factories

* try without useless userinfo in lnd

* Remove monero wallet files

* support simpler DI too

---------

Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
This commit is contained in:
Andrew Camilleri 2023-11-21 10:55:02 +01:00 committed by GitHub
parent 6d288271cd
commit 2f23bad3bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 231 additions and 130 deletions

View File

@ -12,7 +12,8 @@ namespace BTCPayServer.Tests
{
this._Parent = serverTester;
var url = serverTester.GetEnvironment(environmentName, defaultValue);
Client = (ChargeClient)LightningClientFactory.CreateClient(url, network);
Client = (ChargeClient)new LightningClientFactory(network).Create(url);
P2PHost = _Parent.GetEnvironment(environmentName + "_HOST", defaultHost);
}
public ChargeClient Client { get; set; }

View File

@ -295,17 +295,12 @@ namespace BTCPayServer.Tests
public void AddLightningNode()
{
AddLightningNode(null, null, true);
AddLightningNode(null, true);
}
public void AddLightningNode(LightningConnectionType? connectionType = null, bool test = true)
public void AddLightningNode(string? connectionType = null, bool test = true)
{
AddLightningNode(null, connectionType, test);
}
public void AddLightningNode(string cryptoCode = null, LightningConnectionType? connectionType = null, bool test = true)
{
cryptoCode ??= "BTC";
var cryptoCode = "BTC";
if (!Driver.PageSource.Contains("Connect to a Lightning node"))
{
GoToLightningSettings();

View File

@ -94,17 +94,18 @@ namespace BTCPayServer.Tests
{
ActivateLightning(LightningConnectionType.CLightning);
}
public void ActivateLightning(LightningConnectionType internalNode)
public void ActivateLightning(string internalNode)
{
var btc = NetworkProvider.GetNetwork<BTCPayNetwork>("BTC").NBitcoinNetwork;
CustomerLightningD = LightningClientFactory.CreateClient(GetEnvironment("TEST_CUSTOMERLIGHTNINGD", "type=clightning;server=tcp://127.0.0.1:30992/"), btc);
MerchantLightningD = LightningClientFactory.CreateClient(GetEnvironment("TEST_MERCHANTLIGHTNINGD", "type=clightning;server=tcp://127.0.0.1:30993/"), btc);
var factory = new LightningClientFactory(btc);
CustomerLightningD = factory.Create(GetEnvironment("TEST_CUSTOMERLIGHTNINGD", "type=clightning;server=tcp://127.0.0.1:30992/"));
MerchantLightningD = factory.Create(GetEnvironment("TEST_MERCHANTLIGHTNINGD", "type=clightning;server=tcp://127.0.0.1:30993/"));
MerchantCharge = new ChargeTester(this, "TEST_MERCHANTCHARGE", "type=charge;server=http://127.0.0.1:54938/;api-token=foiewnccewuify;allowinsecure=true", "merchant_lightningd", btc);
MerchantLnd = new LndMockTester(this, "TEST_MERCHANTLND", "http://lnd:lnd@127.0.0.1:35531/", "merchant_lnd", btc);
PayTester.UseLightning = true;
PayTester.IntegratedLightning = GetLightningConnectionString(internalNode, true);
}
public string GetLightningConnectionString(LightningConnectionType? connectionType, bool isMerchant)
public string GetLightningConnectionString(string? connectionType, bool isMerchant)
{
string connectionString = null;
if (connectionType is null)

View File

@ -278,7 +278,7 @@ namespace BTCPayServer.Tests
public bool IsAdmin { get; internal set; }
public void RegisterLightningNode(string cryptoCode, LightningConnectionType? connectionType = null, bool isMerchant = true)
public void RegisterLightningNode(string cryptoCode, string? connectionType = null, bool isMerchant = true)
{
RegisterLightningNodeAsync(cryptoCode, connectionType, isMerchant).GetAwaiter().GetResult();
}
@ -286,7 +286,7 @@ namespace BTCPayServer.Tests
{
return RegisterLightningNodeAsync(cryptoCode, null, isMerchant, storeId);
}
public async Task RegisterLightningNodeAsync(string cryptoCode, LightningConnectionType? connectionType, bool isMerchant = true, string storeId = null)
public async Task RegisterLightningNodeAsync(string cryptoCode, string? connectionType, bool isMerchant = true, string storeId = null)
{
var storeController = GetController<UIStoresController>();

View File

@ -23,6 +23,7 @@ using BTCPayServer.Fido2.Models;
using BTCPayServer.HostedServices;
using BTCPayServer.Hosting;
using BTCPayServer.Lightning;
using BTCPayServer.Lightning.Charge;
using BTCPayServer.Models;
using BTCPayServer.Models.AccountViewModels;
using BTCPayServer.Models.AppViewModels;
@ -68,6 +69,7 @@ using Newtonsoft.Json.Schema;
using Xunit;
using Xunit.Abstractions;
using Xunit.Sdk;
using CreateInvoiceRequest = BTCPayServer.Client.Models.CreateInvoiceRequest;
using RatesViewModel = BTCPayServer.Models.StoreViewModels.RatesViewModel;
namespace BTCPayServer.Tests
@ -475,7 +477,7 @@ namespace BTCPayServer.Tests
await ProcessLightningPayment(LightningConnectionType.LndREST);
}
async Task ProcessLightningPayment(LightningConnectionType type)
async Task ProcessLightningPayment(string type)
{
// For easier debugging and testing
// LightningLikePaymentHandler.LIGHTNING_TIMEOUT = int.MaxValue;
@ -2390,9 +2392,14 @@ namespace BTCPayServer.Tests
Assert.NotNull(lnMethod.GetExternalLightningUrl());
var url = lnMethod.GetExternalLightningUrl();
Assert.Equal(LightningConnectionType.Charge, url.ConnectionType);
Assert.Equal("pass", url.Password);
Assert.Equal("usr", url.Username);
var kv = LightningConnectionStringHelper.ExtractValues(url, out var connType);
Assert.Equal(LightningConnectionType.Charge,connType);
var client = Assert.IsType<ChargeClient>(tester.PayTester.GetService<LightningClientFactoryService>()
.Create(url, tester.NetworkProvider.GetNetwork<BTCPayNetwork>("BTC")));
var auth = Assert.IsType<ChargeAuthentication.UserPasswordAuthentication>(client.ChargeAuthentication);
Assert.Equal("pass", auth.NetworkCredential.Password);
Assert.Equal("usr", auth.NetworkCredential.UserName);
// Test if lightning connection strings get migrated to internal
store.DerivationStrategies = new JObject()

View File

@ -24,7 +24,7 @@ services:
TESTS_AzureBlobStorageConnectionString: ${TESTS_AzureBlobStorageConnectionString:-none}
TEST_MERCHANTLIGHTNINGD: "type=clightning;server=unix://etc/merchant_lightningd_datadir/lightning-rpc"
TEST_CUSTOMERLIGHTNINGD: "type=clightning;server=unix://etc/customer_lightningd_datadir/lightning-rpc"
TEST_MERCHANTLND: "http://lnd:lnd@merchant_lnd:8080/"
TEST_MERCHANTLND: "http://merchant_lnd:8080/"
TESTS_INCONTAINER: "true"
TESTS_SSHCONNECTION: "root@sshd:22"
TESTS_SSHPASSWORD: ""

View File

@ -22,7 +22,7 @@ services:
TESTS_AzureBlobStorageConnectionString: ${TESTS_AzureBlobStorageConnectionString:-none}
TEST_MERCHANTLIGHTNINGD: "type=clightning;server=unix://etc/merchant_lightningd_datadir/lightning-rpc"
TEST_CUSTOMERLIGHTNINGD: "type=clightning;server=unix://etc/customer_lightningd_datadir/lightning-rpc"
TEST_MERCHANTLND: "http://lnd:lnd@merchant_lnd:8080/"
TEST_MERCHANTLND: "http://merchant_lnd:8080/"
TESTS_INCONTAINER: "true"
TESTS_SSHCONNECTION: "root@sshd:22"
TESTS_SSHPASSWORD: ""

View File

@ -48,7 +48,7 @@
<PackageReference Include="YamlDotNet" Version="8.0.0" />
<PackageReference Include="BIP78.Sender" Version="0.2.2" />
<PackageReference Include="BTCPayServer.Hwi" Version="2.0.2" />
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.4.31" />
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.5.1" />
<PackageReference Include="CsvHelper" Version="15.0.5" />
<PackageReference Include="Dapper" Version="2.1.21" />
<PackageReference Include="Fido2" Version="2.0.2" />

View File

@ -101,7 +101,7 @@ public class StoreLightningBalance : ViewComponent
}
if (existing.IsInternalNode && _lightningNetworkOptions.Value.InternalLightningByCryptoCode.TryGetValue(cryptoCode, out var internalLightningNode))
{
return _lightningClientFactory.Create(internalLightningNode, network);
return internalLightningNode;
}
return null;

View File

@ -5,7 +5,6 @@ namespace BTCPayServer.Configuration
{
public class LightningNetworkOptions
{
public Dictionary<string, LightningConnectionString> InternalLightningByCryptoCode { get; set; } =
new Dictionary<string, LightningConnectionString>();
public Dictionary<string, ILightningClient> InternalLightningByCryptoCode { get; set; } = new();
}
}

View File

@ -147,7 +147,8 @@ namespace BTCPayServer.Controllers.Greenfield
{
throw ErrorShouldBeAdminForInternalNode();
}
return _lightningClientFactory.Create(internalLightningNode, network);
return internalLightningNode;
}
}
}

View File

@ -156,7 +156,7 @@ namespace BTCPayServer.Controllers.Greenfield
.FirstOrDefault(d => d.PaymentId == id);
if (existing == null)
throw ErrorLightningNodeNotConfiguredForStore();
if (existing.GetExternalLightningUrl() is LightningConnectionString connectionString)
if (existing.GetExternalLightningUrl() is {} connectionString)
{
return Task.FromResult(_lightningClientFactory.Create(connectionString, network));
}
@ -168,7 +168,7 @@ namespace BTCPayServer.Controllers.Greenfield
{
throw ErrorShouldBeAdminForInternalNode();
}
return Task.FromResult(_lightningClientFactory.Create(internalLightningNode, network));
return Task.FromResult(internalLightningNode);
}
throw ErrorLightningNodeNotConfiguredForStore();
}

View File

@ -1,4 +1,5 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
@ -37,17 +38,19 @@ namespace BTCPayServer.Controllers.Greenfield
private readonly StoreRepository _storeRepository;
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
private readonly IAuthorizationService _authorizationService;
private readonly LightningClientFactoryService _lightningClientFactoryService;
public GreenfieldStoreLightningNetworkPaymentMethodsController(
StoreRepository storeRepository,
BTCPayNetworkProvider btcPayNetworkProvider,
IAuthorizationService authorizationService,
ISettingsRepository settingsRepository,
LightningClientFactoryService lightningClientFactoryService,
PoliciesSettings policiesSettings)
{
_storeRepository = storeRepository;
_btcPayNetworkProvider = btcPayNetworkProvider;
_authorizationService = authorizationService;
_lightningClientFactoryService = lightningClientFactoryService;
PoliciesSettings = policiesSettings;
}
@ -155,21 +158,26 @@ namespace BTCPayServer.Controllers.Greenfield
}
else
{
if (!LightningConnectionString.TryParse(request.ConnectionString, false,
out var connectionString, out var error))
ILightningClient? lightningClient;
try
{
ModelState.AddModelError(nameof(request.ConnectionString), $"Invalid URL ({error})");
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
lightningClient = _lightningClientFactoryService.Create(request.ConnectionString, network);
}
catch (Exception e)
{
ModelState.AddModelError(nameof(request.ConnectionString), $"Invalid URL ({e.Message})");
return this.CreateValidationError(ModelState);
}
if (connectionString.ConnectionType == LightningConnectionType.LndGRPC)
{
ModelState.AddModelError(nameof(request.ConnectionString),
$"BTCPay does not support gRPC connections");
return this.CreateValidationError(ModelState);
}
// if (connectionString.ConnectionType == LightningConnectionType.LndGRPC)
// {
// ModelState.AddModelError(nameof(request.ConnectionString),
// $"BTCPay does not support gRPC connections");
// return this.CreateValidationError(ModelState);
// }
if (!await CanManageServer() && !connectionString.IsSafe())
if (!await CanManageServer() && !lightningClient.IsSafe())
{
ModelState.AddModelError(nameof(request.ConnectionString),
$"You do not have 'btcpay.server.canmodifyserversettings' rights, so the connection string should not contain 'cookiefilepath', 'macaroondirectorypath', 'macaroonfilepath', and should not point to a local ip or to a dns name ending with '.internal', '.local', '.lan' or '.'.");
@ -180,7 +188,7 @@ namespace BTCPayServer.Controllers.Greenfield
{
CryptoCode = paymentMethodId.CryptoCode
};
paymentMethod.SetLightningUrl(connectionString);
paymentMethod.SetLightningUrl(lightningClient);
}
}
store.SetSupportedPaymentMethod(paymentMethodId, paymentMethod);

View File

@ -29,14 +29,17 @@ namespace BTCPayServer.Controllers
[HttpPost("i/{invoiceId}/test-payment")]
[CheatModeRoute]
public async Task<IActionResult> TestPayment(string invoiceId, FakePaymentRequest request, [FromServices] Cheater cheater)
public async Task<IActionResult> TestPayment(string invoiceId, FakePaymentRequest request,
[FromServices] Cheater cheater,
[FromServices] LightningClientFactoryService lightningClientFactoryService)
{
var invoice = await _InvoiceRepository.GetInvoice(invoiceId);
var store = await _StoreRepository.FindStore(invoice.StoreId);
var isSats = request.CryptoCode.ToUpper(CultureInfo.InvariantCulture) == "SATS";
var cryptoCode = isSats ? "BTC" : request.CryptoCode;
var amount = new Money(request.Amount, isSats ? MoneyUnit.Satoshi : MoneyUnit.BTC);
var network = _NetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode).NBitcoinNetwork;
var btcpayNetwork = _NetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
var network = btcpayNetwork.NBitcoinNetwork;
var paymentMethodId = new[] { store.GetDefaultPaymentId() }
.Concat(store.GetEnabledPaymentIds(_NetworkProvider))
.FirstOrDefault(p => p?.ToString() == request.PaymentMethodId);
@ -61,8 +64,10 @@ namespace BTCPayServer.Controllers
case LightningPaymentType:
// requires the channels to be set up using the BTCPayServer.Tests/docker-lightning-channel-setup.sh script
LightningConnectionString.TryParse(Environment.GetEnvironmentVariable("BTCPAY_BTCEXTERNALLNDREST"), false, out var lnConnection);
var lnClient = LightningClientFactory.CreateClient(lnConnection, network);
var lnClient = lightningClientFactoryService.Create(
Environment.GetEnvironmentVariable("BTCPAY_BTCEXTERNALLNDREST"),
btcpayNetwork);
var lnAmount = new LightMoney(amount.Satoshi, LightMoneyUnit.Satoshi);
var response = await lnClient.Pay(destination, new PayInvoiceParams { Amount = lnAmount });

View File

@ -140,17 +140,18 @@ namespace BTCPayServer.Controllers
ModelState.AddModelError(nameof(vm.ConnectionString), "Please provide a connection string");
return View(vm);
}
if (!LightningConnectionString.TryParse(vm.ConnectionString, false, out var connectionString, out var error))
ILightningClient? lightningClient = null;
try
{
ModelState.AddModelError(nameof(vm.ConnectionString), $"Invalid URL ({error})");
lightningClient = _lightningClientFactoryService.Create(vm.ConnectionString, network);
}
catch (Exception e)
{
ModelState.AddModelError(nameof(vm.ConnectionString), $"Invalid URL ({e.Message})");
return View(vm);
}
if (connectionString.ConnectionType == LightningConnectionType.LndGRPC)
{
ModelState.AddModelError(nameof(vm.ConnectionString), $"BTCPay does not support gRPC connections");
return View(vm);
}
if (!User.IsInRole(Roles.ServerAdmin) && !connectionString.IsSafe())
if (!User.IsInRole(Roles.ServerAdmin) && !lightningClient.IsSafe())
{
ModelState.AddModelError(nameof(vm.ConnectionString), "You are not a server admin, so the connection string should not contain 'cookiefilepath', 'macaroondirectorypath', 'macaroonfilepath', and should not point to a local ip or to a dns name ending with '.internal', '.local', '.lan' or '.'.");
return View(vm);
@ -163,7 +164,7 @@ namespace BTCPayServer.Controllers
try
{
paymentMethod.SetLightningUrl(connectionString);
paymentMethod.SetLightningUrl(lightningClient);
}
catch (Exception ex)
{

View File

@ -66,7 +66,8 @@ namespace BTCPayServer.Controllers
IDataProtectionProvider dataProtector,
IOptions<LightningNetworkOptions> lightningNetworkOptions,
IOptions<ExternalServicesOptions> externalServiceOptions,
IHtmlHelper html)
IHtmlHelper html,
LightningClientFactoryService lightningClientFactoryService)
{
_RateFactory = rateFactory;
_Repo = repo;
@ -90,6 +91,7 @@ namespace BTCPayServer.Controllers
_BtcpayServerOptions = btcpayServerOptions;
_BTCPayEnv = btcpayEnv;
_externalServiceOptions = externalServiceOptions;
_lightningClientFactoryService = lightningClientFactoryService;
Html = html;
}
@ -112,6 +114,7 @@ namespace BTCPayServer.Controllers
private readonly IFileService _fileService;
private readonly EventAggregator _EventAggregator;
private readonly IOptions<ExternalServicesOptions> _externalServiceOptions;
private readonly LightningClientFactoryService _lightningClientFactoryService;
public string? GeneratedPairingCode { get; set; }
public WebhookSender WebhookNotificationManager { get; }

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.IO;
using System.Linq;
@ -83,19 +84,42 @@ namespace BTCPayServer
return endpoint != null;
}
public static bool IsSafe(this LightningConnectionString connectionString)
public static Uri GetServerUri(this ILightningClient client)
{
if (connectionString.CookieFilePath != null ||
connectionString.MacaroonDirectoryPath != null ||
connectionString.MacaroonFilePath != null)
var kv = LightningConnectionStringHelper.ExtractValues(client.ToString(), out var type);
return !kv.TryGetValue("server", out var server) ? null : new Uri(server, UriKind.Absolute);
}
public static string GetDisplayName(this ILightningClient client)
{
LightningConnectionStringHelper.ExtractValues(client.ToString(), out var type);
var field = typeof(LightningConnectionType).GetField(type, BindingFlags.Public | BindingFlags.Static);
if (field == null) return type;
DisplayAttribute attr = field.GetCustomAttribute<DisplayAttribute>();
return attr?.Name ?? type;
}
public static bool IsSafe(this ILightningClient connectionString)
{
var kv = LightningConnectionStringHelper.ExtractValues(connectionString.ToString(), out var type);
if (kv.TryGetValue("cookiefilepath", out var cookieFilePath) ||
kv.TryGetValue("macaroondirectorypath", out var macaroonDirectoryPath) ||
kv.TryGetValue("macaroonfilepath", out var macaroonFilePath) )
return false;
var uri = connectionString.BaseUri;
if (!kv.TryGetValue("server", out var server))
{
return true;
}
var uri = new Uri(server, UriKind.Absolute);
if (uri.Scheme.Equals("unix", StringComparison.OrdinalIgnoreCase))
return false;
if (!NBitcoin.Utils.TryParseEndpoint(uri.DnsSafeHost, 80, out var endpoint))
if (!Utils.TryParseEndpoint(uri.DnsSafeHost, 80, out var endpoint))
return false;
return !Extensions.IsLocalNetwork(uri.DnsSafeHost);
return !IsLocalNetwork(uri.DnsSafeHost);
}
public static IQueryable<TEntity> Where<TEntity>(this Microsoft.EntityFrameworkCore.DbSet<TEntity> obj, System.Linq.Expressions.Expression<Func<TEntity, bool>> predicate) where TEntity : class

View File

@ -19,6 +19,12 @@ using BTCPayServer.Data.Payouts.LightningLike;
using BTCPayServer.Forms;
using BTCPayServer.HostedServices;
using BTCPayServer.Lightning;
using BTCPayServer.Lightning.Charge;
using BTCPayServer.Lightning.CLightning;
using BTCPayServer.Lightning.Eclair;
using BTCPayServer.Lightning.LNbank;
using BTCPayServer.Lightning.LND;
using BTCPayServer.Lightning.LNDhub;
using BTCPayServer.Logging;
using BTCPayServer.PaymentRequest;
using BTCPayServer.Payments;
@ -122,9 +128,24 @@ namespace BTCPayServer.Hosting
services.AddSingleton<IHostedService>(provider => provider.GetRequiredService<TorServices>());
services.AddSingleton<ISwaggerProvider, DefaultSwaggerProvider>();
services.TryAddSingleton<SocketFactory>();
services.AddSingleton<Func<HttpClient, ILightningConnectionStringHandler>>(client =>
new ChargeLightningConnectionStringHandler(client));
services.AddSingleton<Func<HttpClient, ILightningConnectionStringHandler>>(_ =>
new CLightningConnectionStringHandler());
services.AddSingleton<Func<HttpClient, ILightningConnectionStringHandler>>(client =>
new EclairConnectionStringHandler(client));
services.AddSingleton<Func<HttpClient, ILightningConnectionStringHandler>>(client =>
new LndConnectionStringHandler(client));
services.AddSingleton<Func<HttpClient, ILightningConnectionStringHandler>>(client =>
new LndHubConnectionStringHandler(client));
services.AddSingleton<Func<HttpClient, ILightningConnectionStringHandler>>(client =>
new LNbankConnectionStringHandler(client));
services.TryAddSingleton<LightningClientFactoryService>();
services.AddHttpClient(LightningClientFactoryService.OnionNamedClient)
.ConfigurePrimaryHttpMessageHandler<Socks5HttpClientHandler>();
services.TryAddSingleton<InvoicePaymentNotification>();
services.TryAddSingleton<BTCPayServerOptions>(o =>
o.GetRequiredService<IOptions<BTCPayServerOptions>>().Value);
@ -211,16 +232,26 @@ namespace BTCPayServer.Hosting
}
}
});
services.AddOptions<LightningNetworkOptions>().Configure<BTCPayNetworkProvider>(
(options, btcPayNetworkProvider) =>
services.AddOptions<LightningNetworkOptions>().Configure<BTCPayNetworkProvider, LightningClientFactoryService>(
(options, btcPayNetworkProvider, lightningClientFactoryService) =>
{
foreach (var net in btcPayNetworkProvider.GetAll().OfType<BTCPayNetwork>())
{
var lightning = configuration.GetOrDefault<string>($"{net.CryptoCode}.lightning", string.Empty);
if (lightning.Length != 0)
{
if (!LightningConnectionString.TryParse(lightning, true, out var connectionString,
out var error))
string error = null;
ILightningClient lightningClient = null;
try
{
lightningClient = lightningClientFactoryService.Create(lightning, net);
}
catch (Exception e)
{
error = e.Message;
}
if (error is not null)
{
logs.Configuration.LogWarning($"Invalid setting {net.CryptoCode}.lightning, " +
Environment.NewLine +
@ -241,12 +272,12 @@ namespace BTCPayServer.Hosting
}
else
{
if (connectionString.IsLegacy)
if (lightningClient.ToString() != lightning)
{
logs.Configuration.LogWarning(
$"Setting {net.CryptoCode}.lightning is a deprecated format, it will work now, but please replace it for future versions with '{connectionString.ToString()}'");
$"Setting {net.CryptoCode}.lightning is a deprecated format ({lightning}), it will work now, but please replace it for future versions with '{lightningClient.ToString()}'");
}
options.InternalLightningByCryptoCode.Add(net.CryptoCode, connectionString);
options.InternalLightningByCryptoCode.Add(net.CryptoCode, lightningClient);
}
}
}

View File

@ -1,12 +1,10 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Client;
using BTCPayServer.Client.Models;
using BTCPayServer.Configuration;
using BTCPayServer.Data;
@ -27,12 +25,10 @@ using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using NBitcoin;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using PeterO.Cbor;
using YamlDotNet.RepresentationModel;
using YamlDotNet.Serialization;
using LightningAddressData = BTCPayServer.Data.LightningAddressData;
using Serializer = NBXplorer.Serializer;
@ -50,6 +46,7 @@ namespace BTCPayServer.Hosting
private readonly BTCPayNetworkJsonSerializerSettings _btcPayNetworkJsonSerializerSettings;
private readonly LightningAddressService _lightningAddressService;
private readonly ILogger<MigrationStartupTask> _logger;
private readonly LightningClientFactoryService _lightningClientFactoryService;
private readonly UserManager<ApplicationUser> _userManager;
public IOptions<LightningNetworkOptions> LightningOptions { get; }
@ -65,7 +62,8 @@ namespace BTCPayServer.Hosting
IEnumerable<IPayoutHandler> payoutHandlers,
BTCPayNetworkJsonSerializerSettings btcPayNetworkJsonSerializerSettings,
LightningAddressService lightningAddressService,
ILogger<MigrationStartupTask> logger)
ILogger<MigrationStartupTask> logger,
LightningClientFactoryService lightningClientFactoryService)
{
_DBContextFactory = dbContextFactory;
_StoreRepository = storeRepository;
@ -76,6 +74,7 @@ namespace BTCPayServer.Hosting
_btcPayNetworkJsonSerializerSettings = btcPayNetworkJsonSerializerSettings;
_lightningAddressService = lightningAddressService;
_logger = logger;
_lightningClientFactoryService = lightningClientFactoryService;
_userManager = userManager;
LightningOptions = lightningOptions;
}
@ -1052,15 +1051,20 @@ retry:
private async Task DeprecatedLightningConnectionStringCheck()
{
using var ctx = _DBContextFactory.CreateContext();
await using var ctx = _DBContextFactory.CreateContext();
foreach (var store in await ctx.Stores.AsQueryable().ToArrayAsync())
{
foreach (var method in store.GetSupportedPaymentMethods(_NetworkProvider).OfType<Payments.Lightning.LightningSupportedPaymentMethod>())
foreach (var method in store.GetSupportedPaymentMethods(_NetworkProvider)
.OfType<LightningSupportedPaymentMethod>())
{
var lightning = method.GetExternalLightningUrl();
if (lightning?.IsLegacy is true)
if (lightning is null)
continue;
var client = _lightningClientFactoryService.Create(lightning,
_NetworkProvider.GetNetwork<BTCPayNetwork>(method.PaymentId.CryptoCode));
if (client?.ToString() != lightning)
{
method.SetLightningUrl(lightning);
method.SetLightningUrl(client);
store.SetSupportedPaymentMethod(method);
}
}

View File

@ -19,7 +19,7 @@ namespace BTCPayServer.Payments.Lightning
{
if (!options.InternalLightningByCryptoCode.TryGetValue(network.CryptoCode, out var connectionString))
throw new PaymentMethodUnavailableException("No internal node configured");
return lightningClientFactory.Create(connectionString, network);
return connectionString;
}
}
}

View File

@ -175,17 +175,7 @@ namespace BTCPayServer.Payments.Lightning
public ILightningClient CreateLightningClient(LightningSupportedPaymentMethod supportedPaymentMethod, BTCPayNetwork network)
{
var external = supportedPaymentMethod.GetExternalLightningUrl();
if (external != null)
{
return _lightningClientFactory.Create(external, network);
}
else
{
if (!Options.Value.InternalLightningByCryptoCode.TryGetValue(network.CryptoCode, out var connectionString))
throw new PaymentMethodUnavailableException("No internal node configured");
return _lightningClientFactory.Create(connectionString, network);
}
return supportedPaymentMethod.CreateLightningClient(network, Options.Value, _lightningClientFactory);
}
public async Task TestConnection(NodeInfo nodeInfo, CancellationToken cancellation)

View File

@ -175,7 +175,6 @@ namespace BTCPayServer.Payments.Lightning
if (lnUri == null)
continue;
listenedInvoices.Add(new ListenedInvoice(
lnUri.BaseUri,
invoice.ExpirationTime,
lightningMethod,
lightningSupportedMethod,
@ -359,7 +358,6 @@ namespace BTCPayServer.Payments.Lightning
if (url is null)
continue;
instanceListener.AddListenedInvoice(new ListenedInvoice(
url.BaseUri,
invoice.ExpirationTime,
newPaymentMethodDetails,
supportedMethod,
@ -384,14 +382,12 @@ namespace BTCPayServer.Payments.Lightning
}
private LightningConnectionString? GetLightningUrl(LightningSupportedPaymentMethod supportedMethod)
private string? GetLightningUrl(LightningSupportedPaymentMethod supportedMethod)
{
var url = supportedMethod.GetExternalLightningUrl();
if (url != null)
return url;
if (Options.Value.InternalLightningByCryptoCode.TryGetValue(supportedMethod.CryptoCode, out var conn))
return conn;
return null;
return Options.Value.InternalLightningByCryptoCode.TryGetValue(supportedMethod.CryptoCode, out var conn) ? conn.ToString() : null;
}
TimeSpan _PollInterval = TimeSpan.FromMinutes(1.0);
@ -452,13 +448,13 @@ namespace BTCPayServer.Payments.Lightning
private readonly PaymentService _paymentService;
private readonly LightningClientFactoryService _lightningClientFactory;
public LightningConnectionString ConnectionString { get; }
public string ConnectionString { get; }
public LightningInstanceListener(InvoiceRepository invoiceRepository,
EventAggregator eventAggregator,
LightningClientFactoryService lightningClientFactory,
BTCPayNetwork network,
LightningConnectionString connectionString,
string connectionString,
PaymentService paymentService,
Logs logs)
{
@ -504,16 +500,18 @@ namespace BTCPayServer.Payments.Lightning
public CancellationTokenSource? StopListeningCancellationTokenSource;
async Task Listen(CancellationToken cancellation)
{
Logs.PayServer.LogInformation($"{_network.CryptoCode} (Lightning): Start listening {ConnectionString.BaseUri}");
Uri? uri = null;
try
{
var lightningClient = _lightningClientFactory.Create(ConnectionString, _network);
uri = lightningClient.GetServerUri();
Logs.PayServer.LogInformation($"{_network.CryptoCode} (Lightning): Start listening {uri}");
using var session = await lightningClient.Listen(cancellation);
// Just in case the payment arrived after our last poll but before we listened.
await PollAllListenedInvoices(cancellation);
if (_ErrorAlreadyLogged)
{
Logs.PayServer.LogInformation($"{_network.CryptoCode} (Lightning): Could reconnect successfully to {ConnectionString.BaseUri}");
Logs.PayServer.LogInformation($"{_network.CryptoCode} (Lightning): Could reconnect successfully to {uri}");
}
_ErrorAlreadyLogged = false;
while (!_ListenedInvoices.IsEmpty)
@ -542,15 +540,16 @@ namespace BTCPayServer.Payments.Lightning
}
}
}
catch (Exception ex) when (!cancellation.IsCancellationRequested && !_ErrorAlreadyLogged)
{
_ErrorAlreadyLogged = true;
Logs.PayServer.LogError(ex, $"{_network.CryptoCode} (Lightning): Error while contacting {ConnectionString.BaseUri}");
Logs.PayServer.LogInformation($"{_network.CryptoCode} (Lightning): Stop listening {ConnectionString.BaseUri}");
Logs.PayServer.LogError(ex, $"{_network.CryptoCode} (Lightning): Error while contacting {uri}");
Logs.PayServer.LogInformation($"{_network.CryptoCode} (Lightning): Stop listening {uri}");
}
catch (OperationCanceledException) when (cancellation.IsCancellationRequested) { }
if (_ListenedInvoices.IsEmpty)
Logs.PayServer.LogInformation($"{_network.CryptoCode} (Lightning): No more invoice to listen on {ConnectionString.BaseUri}, releasing the connection.");
Logs.PayServer.LogInformation($"{_network.CryptoCode} (Lightning): No more invoice to listen on {uri}, releasing the connection.");
}
public DateTimeOffset? LastFullPoll { get; set; }
@ -609,7 +608,6 @@ namespace BTCPayServer.Payments.Lightning
}
public record ListenedInvoice(
Uri Uri,
DateTimeOffset Expiration,
LightningLikePaymentMethodDetails PaymentMethodDetails,
LightningSupportedPaymentMethod SupportedPaymentMethod,

View File

@ -17,20 +17,20 @@ namespace BTCPayServer.Payments.Lightning
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public string? LightningConnectionString { get; set; }
public LightningConnectionString? GetExternalLightningUrl()
public string? GetExternalLightningUrl()
{
#pragma warning disable CS0618 // Type or member is obsolete
if (string.IsNullOrEmpty(LightningConnectionString))
return null;
if (!BTCPayServer.Lightning.LightningConnectionString.TryParse(LightningConnectionString, false, out var connectionString, out var error))
{
throw new FormatException(error);
}
return connectionString;
// if (!BTCPayServer.Lightning.LightningConnectionString.TryParse(LightningConnectionString, false, out var connectionString, out var error))
// {
// throw new FormatException(error);
// }
return LightningConnectionString;
#pragma warning restore CS0618 // Type or member is obsolete
}
public void SetLightningUrl(LightningConnectionString connectionString)
public void SetLightningUrl(ILightningClient connectionString)
{
ArgumentNullException.ThrowIfNull(connectionString);
#pragma warning disable CS0618 // Type or member is obsolete
@ -41,9 +41,8 @@ namespace BTCPayServer.Payments.Lightning
public string GetDisplayableConnectionString()
{
#pragma warning disable CS0618 // Type or member is obsolete
if (!string.IsNullOrEmpty(LightningConnectionString) &&
BTCPayServer.Lightning.LightningConnectionString.TryParse(LightningConnectionString, false, out var conn))
return conn.ToString();
if (!string.IsNullOrEmpty(LightningConnectionString))
return LightningConnectionString;
#pragma warning restore CS0618 // Type or member is obsolete
if (InternalNodeRef is string s)
return s;

View File

@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using BTCPayServer.Lightning;
@ -8,24 +10,40 @@ namespace BTCPayServer.Services
{
private readonly IHttpClientFactory _httpClientFactory;
public LightningClientFactoryService(IHttpClientFactory httpClientFactory)
private readonly IEnumerable<Func<HttpClient, ILightningConnectionStringHandler>>
_lightningConnectionStringHandlersFactories;
private readonly IEnumerable<ILightningConnectionStringHandler> _lightningConnectionStringHandlers;
public LightningClientFactoryService(IHttpClientFactory httpClientFactory,
IEnumerable<Func<HttpClient, ILightningConnectionStringHandler>> lightningConnectionStringHandlersFactories, IEnumerable<ILightningConnectionStringHandler> lightningConnectionStringHandlers)
{
_httpClientFactory = httpClientFactory;
_lightningConnectionStringHandlersFactories = lightningConnectionStringHandlersFactories;
_lightningConnectionStringHandlers = lightningConnectionStringHandlers;
}
private LightningClientFactory GetFactory(string namedClient, BTCPayNetwork network)
{
var httpClient = _httpClientFactory.CreateClient(namedClient);
return new LightningClientFactory(_lightningConnectionStringHandlersFactories
.Select(handler => handler(httpClient)).Concat(_lightningConnectionStringHandlers)
.ToArray(), network.NBitcoinNetwork);
}
public static string OnionNamedClient { get; set; } = "lightning.onion";
public ILightningClient Create(LightningConnectionString lightningConnectionString, BTCPayNetwork network)
public ILightningClient Create(string lightningConnectionString, BTCPayNetwork network)
{
ArgumentNullException.ThrowIfNull(lightningConnectionString);
ArgumentNullException.ThrowIfNull(network);
return new LightningClientFactory(network.NBitcoinNetwork)
{
HttpClient = _httpClientFactory.CreateClient(lightningConnectionString.BaseUri.IsOnion()
? OnionNamedClient
: $"{network.CryptoCode}: Lightning client")
}.Create(lightningConnectionString);
var httpClient = lightningConnectionString.Contains(".onion")
? OnionNamedClient
: $"{network.CryptoCode}: Lightning client";
return GetFactory(httpClient, network).Create(lightningConnectionString);
}
}
}

View File

@ -85,7 +85,7 @@
<td>@payment.Crypto</td>
<td>@(payment.CryptoPaymentData.KeyPath?.ToString()?? "Unknown")</td>
<td>
<vc:truncate-center text="@payment.DepositAddress" classes="truncate-center-id" />
<vc:truncate-center text="@payment.DepositAddress.ToString()" classes="truncate-center-id" />
</td>
<td>
<vc:truncate-center text="@payment.PaymentProof" link="@payment.TransactionLink" classes="truncate-center-id" />

View File

@ -1,5 +1,6 @@
@using BTCPayServer.Lightning
@using BTCPayServer.Client
@using BTCPayServer.Services
@model LightningViewModel
@{
Layout = "../Shared/_NavLayout.cshtml";
@ -7,6 +8,8 @@
ViewData.SetActivePage(StoreNavPages.Lightning, $"{Model.CryptoCode} Lightning", Context.GetStoreData().Id);
}
@inject LightningClientFactoryService LightningClientFactoryService
@inject BTCPayNetworkProvider NetworkProvider
<div class="mb-5">
<h3 class="mb-3">@ViewData["Title"]</h3>
<div class="mb-3">
@ -15,12 +18,17 @@
@if (Model.LightningNodeType != LightningNodeType.Internal)
{
<span class="me-3" id="CustomNodeInfo">
@if (LightningConnectionString.TryParse(Model.ConnectionString, out var cs))
@try
{
@typeof(LightningConnectionType).DisplayName(cs.ConnectionType.ToString())
<span>(@cs.BaseUri.Host)</span>
var client = LightningClientFactoryService.Create(Model.ConnectionString, NetworkProvider.GetNetwork<BTCPayNetwork>(Model.CryptoCode));
client.GetDisplayName();
var uri = client.GetServerUri();
if (uri is not null)
{
<span>(@uri.Host)</span>
}
}
else
catch (Exception e)
{
@Model.ConnectionString
}

View File

@ -1,5 +1,8 @@
@using BTCPayServer.Lightning
@using BTCPayServer.Services
@model LightningSettingsViewModel
@inject LightningClientFactoryService LightningClientFactoryService
@inject BTCPayNetworkProvider NetworkProvider
@{
Layout = "../Shared/_NavLayout.cshtml";
ViewData["NavPartialName"] = "../UILightning/_Nav";
@ -15,12 +18,17 @@
@if (Model.LightningNodeType != LightningNodeType.Internal)
{
<span class="me-3" id="CustomNodeInfo">
@if (LightningConnectionString.TryParse(Model.ConnectionString, out var cs))
@try
{
@typeof(LightningConnectionType).DisplayName(cs.ConnectionType.ToString())
<span>(@cs.BaseUri.Host)</span>
var client = LightningClientFactoryService.Create(Model.ConnectionString, NetworkProvider.GetNetwork<BTCPayNetwork>(Model.CryptoCode));
client.GetDisplayName();
var uri = client.GetServerUri();
if (uri is not null)
{
<span>(@uri.Host)</span>
}
}
else
catch (Exception e)
{
@Model.ConnectionString
}