Make LightningListener nullable, fix some NRE

This commit is contained in:
nicolas.dorier 2023-04-27 13:37:10 +09:00
parent 0c78e9e4ac
commit 6193835ea1
No known key found for this signature in database
GPG key ID: 6618763EF09186FE

View file

@ -1,3 +1,4 @@
#nullable enable
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
@ -38,7 +39,7 @@ namespace BTCPayServer.Payments.Lightning
private readonly StoreRepository _storeRepository; private readonly StoreRepository _storeRepository;
private readonly PaymentService _paymentService; private readonly PaymentService _paymentService;
readonly Channel<string> _CheckInvoices = Channel.CreateUnbounded<string>(); readonly Channel<string> _CheckInvoices = Channel.CreateUnbounded<string>();
Task _CheckingInvoice; Task? _CheckingInvoice;
readonly Dictionary<(string, string), LightningInstanceListener> _InstanceListeners = new Dictionary<(string, string), LightningInstanceListener>(); readonly Dictionary<(string, string), LightningInstanceListener> _InstanceListeners = new Dictionary<(string, string), LightningInstanceListener>();
public LightningListener(EventAggregator aggregator, public LightningListener(EventAggregator aggregator,
@ -87,12 +88,15 @@ namespace BTCPayServer.Payments.Lightning
var invoice = await GetInvoice(invoiceId); var invoice = await GetInvoice(invoiceId);
foreach (var listenedInvoice in GetListenedInvoices(invoice)) foreach (var listenedInvoice in GetListenedInvoices(invoice))
{ {
var instanceListenerKey = (listenedInvoice.Network.CryptoCode, GetLightningUrl(listenedInvoice.SupportedPaymentMethod).ToString()); var connStr = GetLightningUrl(listenedInvoice.SupportedPaymentMethod);
if (connStr is null)
continue;
var instanceListenerKey = (listenedInvoice.Network.CryptoCode, connStr.ToString());
lock (_InstanceListeners) lock (_InstanceListeners)
{ {
if (!_InstanceListeners.TryGetValue(instanceListenerKey, out var instanceListener)) if (!_InstanceListeners.TryGetValue(instanceListenerKey, out var instanceListener))
{ {
instanceListener ??= new LightningInstanceListener(_InvoiceRepository, _Aggregator, lightningClientFactory, listenedInvoice.Network, GetLightningUrl(listenedInvoice.SupportedPaymentMethod), _paymentService, Logs); instanceListener ??= new LightningInstanceListener(_InvoiceRepository, _Aggregator, lightningClientFactory, listenedInvoice.Network, connStr, _paymentService, Logs);
_InstanceListeners.TryAdd(instanceListenerKey, instanceListener); _InstanceListeners.TryAdd(instanceListenerKey, instanceListener);
} }
instanceListener.AddListenedInvoice(listenedInvoice); instanceListener.AddListenedInvoice(listenedInvoice);
@ -143,7 +147,7 @@ namespace BTCPayServer.Payments.Lightning
.Where(c => new[] { PaymentTypes.LightningLike, LNURLPayPaymentType.Instance }.Contains(c.GetId().PaymentType))) .Where(c => new[] { PaymentTypes.LightningLike, LNURLPayPaymentType.Instance }.Contains(c.GetId().PaymentType)))
{ {
LightningLikePaymentMethodDetails lightningMethod; LightningLikePaymentMethodDetails lightningMethod;
LightningSupportedPaymentMethod lightningSupportedMethod; LightningSupportedPaymentMethod? lightningSupportedMethod;
switch (paymentMethod.GetPaymentMethodDetails()) switch (paymentMethod.GetPaymentMethodDetails())
{ {
case LNURLPayPaymentMethodDetails lnurlPayPaymentMethodDetails: case LNURLPayPaymentMethodDetails lnurlPayPaymentMethodDetails:
@ -167,16 +171,17 @@ namespace BTCPayServer.Payments.Lightning
continue; continue;
var network = _NetworkProvider.GetNetwork<BTCPayNetwork>(paymentMethod.GetId().CryptoCode); var network = _NetworkProvider.GetNetwork<BTCPayNetwork>(paymentMethod.GetId().CryptoCode);
listenedInvoices.Add(new ListenedInvoice() var lnUri = GetLightningUrl(lightningSupportedMethod);
{ if (lnUri == null)
Expiration = invoice.ExpirationTime, continue;
Uri = GetLightningUrl(lightningSupportedMethod).BaseUri.AbsoluteUri, listenedInvoices.Add(new ListenedInvoice(
PaymentMethodDetails = lightningMethod, lnUri.BaseUri,
SupportedPaymentMethod = lightningSupportedMethod, invoice.ExpirationTime,
PaymentMethod = paymentMethod, lightningMethod,
Network = network, lightningSupportedMethod,
InvoiceId = invoice.Id paymentMethod,
}); network,
invoice.Id));
} }
return listenedInvoices; return listenedInvoices;
} }
@ -262,6 +267,8 @@ namespace BTCPayServer.Payments.Lightning
.Where(method => new[] { PaymentTypes.LightningLike, LNURLPayPaymentType.Instance }.Contains(method.GetId().PaymentType)) .Where(method => new[] { PaymentTypes.LightningLike, LNURLPayPaymentType.Instance }.Contains(method.GetId().PaymentType))
.ToArray(); .ToArray();
var store = await _storeRepository.FindStore(invoice.StoreId); var store = await _storeRepository.FindStore(invoice.StoreId);
if (store is null)
return;
if (paymentMethods.Any()) if (paymentMethods.Any())
{ {
var logs = new InvoiceLogs(); var logs = new InvoiceLogs();
@ -334,9 +341,11 @@ namespace BTCPayServer.Payments.Lightning
.CreatePaymentMethodDetails(logs, supportedMethod, paymentMethod, store, .CreatePaymentMethodDetails(logs, supportedMethod, paymentMethod, store,
paymentMethod.Network, prepObj, pmis)); paymentMethod.Network, prepObj, pmis));
var instanceListenerKey = (paymentMethod.Network.CryptoCode, var connStr = GetLightningUrl(supportedMethod);
GetLightningUrl(supportedMethod).ToString()); if (connStr is null)
LightningInstanceListener instanceListener; continue;
var instanceListenerKey = (paymentMethod.Network.CryptoCode, connStr.ToString());
LightningInstanceListener? instanceListener;
lock (_InstanceListeners) lock (_InstanceListeners)
{ {
_InstanceListeners.TryGetValue(instanceListenerKey, out instanceListener); _InstanceListeners.TryGetValue(instanceListenerKey, out instanceListener);
@ -346,16 +355,17 @@ namespace BTCPayServer.Payments.Lightning
await _InvoiceRepository.NewPaymentDetails(invoice.Id, newPaymentMethodDetails, await _InvoiceRepository.NewPaymentDetails(invoice.Id, newPaymentMethodDetails,
paymentMethod.Network); paymentMethod.Network);
instanceListener.AddListenedInvoice(new ListenedInvoice() var url = GetLightningUrl(supportedMethod);
{ if (url is null)
Expiration = invoice.ExpirationTime, continue;
Uri = GetLightningUrl(supportedMethod).BaseUri.AbsoluteUri, instanceListener.AddListenedInvoice(new ListenedInvoice(
PaymentMethodDetails = newPaymentMethodDetails, url.BaseUri,
SupportedPaymentMethod = supportedMethod, invoice.ExpirationTime,
PaymentMethod = paymentMethod, newPaymentMethodDetails,
Network = (BTCPayNetwork)paymentMethod.Network, supportedMethod,
InvoiceId = invoice.Id paymentMethod,
}); (BTCPayNetwork)paymentMethod.Network,
invoice.Id));
_Aggregator.Publish(new Events.InvoiceNewPaymentDetailsEvent(invoice.Id, _Aggregator.Publish(new Events.InvoiceNewPaymentDetailsEvent(invoice.Id,
newPaymentMethodDetails, paymentMethod.GetId())); newPaymentMethodDetails, paymentMethod.GetId()));
@ -374,14 +384,14 @@ namespace BTCPayServer.Payments.Lightning
} }
private LightningConnectionString GetLightningUrl(LightningSupportedPaymentMethod supportedMethod) private LightningConnectionString? GetLightningUrl(LightningSupportedPaymentMethod supportedMethod)
{ {
var url = supportedMethod.GetExternalLightningUrl(); var url = supportedMethod.GetExternalLightningUrl();
if (url != null) if (url != null)
return url; return url;
if (Options.Value.InternalLightningByCryptoCode.TryGetValue(supportedMethod.CryptoCode, out var conn)) if (Options.Value.InternalLightningByCryptoCode.TryGetValue(supportedMethod.CryptoCode, out var conn))
return conn; return conn;
throw new InvalidOperationException($"{supportedMethod.CryptoCode}: The internal lightning node is not set up"); return null;
} }
TimeSpan _PollInterval = TimeSpan.FromMinutes(1.0); TimeSpan _PollInterval = TimeSpan.FromMinutes(1.0);
@ -400,7 +410,7 @@ namespace BTCPayServer.Payments.Lightning
} }
} }
} }
private Timer _ListenPoller; private Timer? _ListenPoller;
public IOptions<LightningNetworkOptions> Options { get; } public IOptions<LightningNetworkOptions> Options { get; }
@ -412,6 +422,7 @@ namespace BTCPayServer.Payments.Lightning
_Cts.Cancel(); _Cts.Cancel();
try try
{ {
if (_CheckingInvoice != null)
await _CheckingInvoice; await _CheckingInvoice;
} }
catch (OperationCanceledException) catch (OperationCanceledException)
@ -420,7 +431,7 @@ namespace BTCPayServer.Payments.Lightning
} }
try try
{ {
await Task.WhenAll(_ListeningInstances.Select(c => c.Value.Listening).ToArray()); await Task.WhenAll(_ListeningInstances.Select(c => c.Value.Listening).Where(c => c != null).ToArray()!);
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
@ -479,7 +490,7 @@ namespace BTCPayServer.Payments.Lightning
public bool Empty => _ListenedInvoices.IsEmpty; public bool Empty => _ListenedInvoices.IsEmpty;
public bool IsListening => Listening?.Status is TaskStatus.Running || Listening?.Status is TaskStatus.WaitingForActivation; public bool IsListening => Listening?.Status is TaskStatus.Running || Listening?.Status is TaskStatus.WaitingForActivation;
public Task Listening { get; set; } public Task? Listening { get; set; }
public void EnsureListening(CancellationToken cancellation) public void EnsureListening(CancellationToken cancellation)
{ {
if (!IsListening) if (!IsListening)
@ -490,7 +501,7 @@ namespace BTCPayServer.Payments.Lightning
Listening = Listen(StopListeningCancellationTokenSource.Token); Listening = Listen(StopListeningCancellationTokenSource.Token);
} }
} }
public CancellationTokenSource StopListeningCancellationTokenSource; public CancellationTokenSource? StopListeningCancellationTokenSource;
async Task Listen(CancellationToken cancellation) async Task Listen(CancellationToken cancellation)
{ {
Logs.PayServer.LogInformation($"{_network.CryptoCode} (Lightning): Start listening {ConnectionString.BaseUri}"); Logs.PayServer.LogInformation($"{_network.CryptoCode} (Lightning): Start listening {ConnectionString.BaseUri}");
@ -566,6 +577,8 @@ namespace BTCPayServer.Payments.Lightning
public async Task<bool> AddPayment(LightningInvoice notification, string invoiceId, PaymentType paymentType) public async Task<bool> AddPayment(LightningInvoice notification, string invoiceId, PaymentType paymentType)
{ {
if (notification?.PaidAt is null)
return false;
var payment = await _paymentService.AddPayment(invoiceId, notification.PaidAt.Value, new LightningLikePaymentData var payment = await _paymentService.AddPayment(invoiceId, notification.PaidAt.Value, new LightningLikePaymentData
{ {
BOLT11 = notification.BOLT11, BOLT11 = notification.BOLT11,
@ -595,15 +608,15 @@ namespace BTCPayServer.Payments.Lightning
} }
} }
class ListenedInvoice public record ListenedInvoice(
Uri Uri,
DateTimeOffset Expiration,
LightningLikePaymentMethodDetails PaymentMethodDetails,
LightningSupportedPaymentMethod SupportedPaymentMethod,
PaymentMethod PaymentMethod,
BTCPayNetwork Network,
string InvoiceId)
{ {
public bool IsExpired() { return DateTimeOffset.UtcNow > Expiration; } public bool IsExpired() { return DateTimeOffset.UtcNow > Expiration; }
public DateTimeOffset Expiration { get; set; }
public LightningLikePaymentMethodDetails PaymentMethodDetails { get; set; }
public LightningSupportedPaymentMethod SupportedPaymentMethod { get; set; }
public PaymentMethod PaymentMethod { get; set; }
public string Uri { get; internal set; }
public BTCPayNetwork Network { get; internal set; }
public string InvoiceId { get; internal set; }
} }
} }