Abstract ChargeClient to prepare for support of other lightning implementation

This commit is contained in:
nicolas.dorier 2018-03-20 11:59:43 +09:00
parent 73cc75fe66
commit 2b2e12b290
20 changed files with 133 additions and 54 deletions

View file

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Text;
using BTCPayServer.Payments.Lightning.Charge;
using BTCPayServer.Payments.Lightning.CLightning;
using NBitcoin;

View file

@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using BTCPayServer.Payments.Lightning.CLightning;
using BTCPayServer.Payments.Lightning.CLightning.RPC;
using NBitcoin;
namespace BTCPayServer.Tests

View file

@ -18,7 +18,8 @@ using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Globalization;
using BTCPayServer.Payments.Lightning.CLightning.RPC;
using BTCPayServer.Payments.Lightning.CLightning;
using BTCPayServer.Payments.Lightning.Charge;
namespace BTCPayServer.Tests
{
@ -117,7 +118,7 @@ namespace BTCPayServer.Tests
}
}
private async Task<Payments.Lightning.CLightning.GetInfoResponse> WaitLNSynched()
private async Task<GetInfoResponse> WaitLNSynched()
{
while (true)
{

View file

@ -68,7 +68,6 @@ namespace BTCPayServer
public BTCPayDefaultSettings DefaultSettings { get; set; }
public KeyPath CoinType { get; internal set; }
public int MaxTrackedConfirmation { get; internal set; } = 6;
public string CLightningNetworkName { get; internal set; }
public override string ToString()
{

View file

@ -28,10 +28,7 @@ namespace BTCPayServer
CryptoImagePath = "imlegacy/bitcoin-symbol.svg",
LightningImagePath = "imlegacy/btc-lightning.svg",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NBXplorerNetworkProvider.ChainType),
CoinType = NBXplorerNetworkProvider.ChainType == ChainType.Main ? new KeyPath("0'") : new KeyPath("1'"),
CLightningNetworkName = ChainType == ChainType.Main ? "bitcoin" :
ChainType == ChainType.Test ? "testnet" :
ChainType == ChainType.Regtest ? "regtest" : null
CoinType = NBXplorerNetworkProvider.ChainType == ChainType.Main ? new KeyPath("0'") : new KeyPath("1'")
});
}
}

View file

@ -27,9 +27,7 @@ namespace BTCPayServer
CryptoImagePath = "imlegacy/litecoin-symbol.svg",
LightningImagePath = "imlegacy/ltc-lightning.svg",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NBXplorerNetworkProvider.ChainType),
CoinType = NBXplorerNetworkProvider.ChainType == ChainType.Main ? new KeyPath("2'") : new KeyPath("3'"),
CLightningNetworkName = ChainType == ChainType.Main ? "litecoin" :
ChainType == ChainType.Test ? "litecoin-testnet" : null
CoinType = NBXplorerNetworkProvider.ChainType == ChainType.Main ? new KeyPath("2'") : new KeyPath("3'")
});
}
}

View file

@ -37,7 +37,7 @@ namespace BTCPayServer.Controllers
var network = vm.CryptoCurrency == null ? null : _ExplorerProvider.GetNetwork(vm.CryptoCurrency);
vm.SetCryptoCurrencies(_NetworkProvider, vm.CryptoCurrency);
vm.InternalLightningNode = GetInternalLightningNodeIfAuthorized();
if (network == null || network.CLightningNetworkName == null)
if (network == null)
{
ModelState.AddModelError(nameof(vm.CryptoCurrency), "Invalid network");
return View(vm);

View file

@ -147,7 +147,7 @@ namespace BTCPayServer.Hosting
services.AddSingleton<IHostedService, Payments.Bitcoin.NBXplorerListener>();
services.AddSingleton<Payments.IPaymentMethodHandler<Payments.Lightning.LightningSupportedPaymentMethod>, Payments.Lightning.LightningLikePaymentHandler>();
services.AddSingleton<IHostedService, Payments.Lightning.ChargeListener>();
services.AddSingleton<IHostedService, Payments.Lightning.LightningListener>();
services.AddSingleton<IHostedService, NBXplorerWaiters>();
services.AddSingleton<IHostedService, InvoiceNotificationManager>();

View file

@ -34,7 +34,6 @@ namespace BTCPayServer.Models.StoreViewModels
public void SetCryptoCurrencies(BTCPayNetworkProvider networkProvider, string selectedScheme)
{
var choices = networkProvider.GetAll()
.Where(n => n.CLightningNetworkName != null)
.Select(o => new Format() { Name = o.CryptoCode, Value = o.CryptoCode }).ToArray();
var chosen = choices.FirstOrDefault(f => f.Name == selectedScheme) ?? choices.FirstOrDefault();
CryptoCurrencies = new SelectList(choices, nameof(chosen.Value), nameof(chosen.Name), chosen);

View file

@ -6,12 +6,13 @@ using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using BTCPayServer.Payments.Lightning.Charge;
using NBitcoin;
using NBitcoin.RPC;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Payments.Lightning.CLightning.RPC
namespace BTCPayServer.Payments.Lightning.CLightning
{
public class CLightningRPCClient
{

View file

@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace BTCPayServer.Payments.Lightning.CLightning.RPC
namespace BTCPayServer.Payments.Lightning.CLightning
{
public class NodeInfo
{

View file

@ -5,7 +5,7 @@ using System.Threading.Tasks;
using NBitcoin;
using Newtonsoft.Json;
namespace BTCPayServer.Payments.Lightning.CLightning.RPC
namespace BTCPayServer.Payments.Lightning.CLightning
{
public class ChannelInfo
{

View file

@ -13,9 +13,9 @@ using NBitcoin;
using NBXplorer;
using Newtonsoft.Json;
namespace BTCPayServer.Payments.Lightning.CLightning
namespace BTCPayServer.Payments.Lightning.Charge
{
public class ChargeClient
public class ChargeClient : ILightningInvoiceClient
{
private Uri _Uri;
public Uri Uri
@ -126,5 +126,48 @@ namespace BTCPayServer.Payments.Lightning.CLightning
uri += "/";
return new Uri(uri + partialUrl);
}
async Task<LightningInvoice> ILightningInvoiceClient.GetInvoice(string invoiceId, CancellationToken cancellation)
{
var invoice = await GetInvoice(invoiceId, cancellation);
return ChargeClient.ToLightningInvoice(invoice);
}
async Task<ILightningListenInvoiceSession> ILightningInvoiceClient.Listen(CancellationToken cancellation)
{
return await Listen(cancellation);
}
internal static LightningInvoice ToLightningInvoice(ChargeInvoice invoice)
{
return new LightningInvoice()
{
Id = invoice.Id,
Amount = invoice.MilliSatoshi,
BOLT11 = invoice.PaymentRequest,
PaidAt = invoice.PaidAt,
Status = invoice.Status
};
}
async Task<LightningInvoice> ILightningInvoiceClient.CreateInvoice(LightMoney amount, TimeSpan expiry, CancellationToken cancellation)
{
var invoice = await CreateInvoiceAsync(new CreateInvoiceRequest() { Amont = amount, Expiry = expiry });
return new LightningInvoice() { Id = invoice.Id, Amount = amount, BOLT11 = invoice.PayReq, Status = "unpaid" };
}
async Task<LightningNodeInformation> ILightningInvoiceClient.GetInfo(CancellationToken cancellation)
{
var info = await GetInfoAsync(cancellation);
var address = info.Address.Select(a => a.Address).FirstOrDefault();
var port = info.Port;
address = address ?? Uri.DnsSafeHost;
return new LightningNodeInformation()
{
P2PPort = port,
Address = address,
BlockHeight = info.BlockHeight
};
}
}
}

View file

@ -8,7 +8,7 @@ using System.Threading.Tasks;
using NBXplorer;
using Newtonsoft.Json;
namespace BTCPayServer.Payments.Lightning.CLightning
namespace BTCPayServer.Payments.Lightning.Charge
{
public class ChargeInvoice
{
@ -28,7 +28,7 @@ namespace BTCPayServer.Payments.Lightning.CLightning
[JsonProperty("payreq")]
public string PaymentRequest { get; set; }
}
public class ChargeSession : IDisposable
public class ChargeSession : ILightningListenInvoiceSession
{
private ClientWebSocket socket;
@ -42,7 +42,7 @@ namespace BTCPayServer.Payments.Lightning.CLightning
}
ArraySegment<byte> _Buffer;
public async Task<ChargeInvoice> NextEvent(CancellationToken cancellation = default(CancellationToken))
public async Task<ChargeInvoice> WaitInvoice(CancellationToken cancellation = default(CancellationToken))
{
var buffer = _Buffer;
var array = _Buffer.Array;
@ -120,5 +120,15 @@ namespace BTCPayServer.Payments.Lightning.CLightning
{
await this.socket.CloseSocket();
}
async Task<LightningInvoice> ILightningListenInvoiceSession.WaitInvoice(CancellationToken token)
{
return ChargeClient.ToLightningInvoice(await WaitInvoice(token));
}
void IDisposable.Dispose()
{
Dispose();
}
}
}

View file

@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace BTCPayServer.Payments.Lightning.CLightning
namespace BTCPayServer.Payments.Lightning.Charge
{
public class CreateInvoiceRequest
{

View file

@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace BTCPayServer.Payments.Lightning.CLightning
namespace BTCPayServer.Payments.Lightning.Charge
{
public class CreateInvoiceResponse
{

View file

@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace BTCPayServer.Payments.Lightning.CLightning
namespace BTCPayServer.Payments.Lightning.Charge
{
//[{"type":"ipv4","address":"52.166.90.122","port":9735}]
public class GetInfoResponse

View file

@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Payments.Lightning.Charge;
namespace BTCPayServer.Payments.Lightning
{
public class LightningInvoice
{
public string Id { get; set; }
public string Status { get; set; }
public string BOLT11 { get; set; }
public DateTimeOffset? PaidAt
{
get; set;
}
public LightMoney Amount { get; set; }
}
public class LightningNodeInformation
{
public string Address { get; internal set; }
public int P2PPort { get; internal set; }
public int BlockHeight { get; set; }
}
public interface ILightningInvoiceClient
{
Task<LightningInvoice> GetInvoice(string invoiceId, CancellationToken cancellation = default(CancellationToken));
Task<LightningInvoice> CreateInvoice(LightMoney amount, TimeSpan expiry, CancellationToken cancellation = default(CancellationToken));
Task<ILightningListenInvoiceSession> Listen(CancellationToken cancellation = default(CancellationToken));
Task<LightningNodeInformation> GetInfo(CancellationToken cancellation = default(CancellationToken));
}
public interface ILightningListenInvoiceSession : IDisposable
{
Task<LightningInvoice> WaitInvoice(CancellationToken token);
}
}

View file

@ -6,6 +6,7 @@ using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.HostedServices;
using BTCPayServer.Payments.Lightning.Charge;
using BTCPayServer.Payments.Lightning.CLightning;
using BTCPayServer.Services.Invoices;
@ -24,14 +25,12 @@ namespace BTCPayServer.Payments.Lightning
var due = Extensions.RoundUp(invoice.ProductInformation.Price / paymentMethod.Rate, 8);
var client = GetClient(supportedPaymentMethod, network);
var expiry = invoice.ExpirationTime - DateTimeOffset.UtcNow;
var lightningInvoice = await client.CreateInvoiceAsync(new CreateInvoiceRequest()
{
Amont = new LightMoney(due, LightMoneyUnit.BTC),
Expiry = expiry < TimeSpan.Zero ? TimeSpan.FromSeconds(1) : expiry
});
if (expiry < TimeSpan.Zero)
expiry = TimeSpan.FromSeconds(1);
var lightningInvoice = await client.CreateInvoice(new LightMoney(due, LightMoneyUnit.BTC), expiry);
return new LightningLikePaymentMethodDetails()
{
BOLT11 = lightningInvoice.PayReq,
BOLT11 = lightningInvoice.BOLT11,
InvoiceId = lightningInvoice.Id
};
}
@ -54,23 +53,15 @@ namespace BTCPayServer.Payments.Lightning
var cts = new CancellationTokenSource(5000);
var client = GetClient(supportedPaymentMethod, network);
GetInfoResponse info = null;
LightningNodeInformation info = null;
try
{
info = await client.GetInfoAsync(cts.Token);
info = await client.GetInfo(cts.Token);
}
catch (Exception ex)
{
throw new Exception($"Error while connecting to the lightning charge {client.Uri} ({ex.Message})");
}
var address = info.Address.Select(a=>a.Address).FirstOrDefault();
var port = info.Port;
address = address ?? client.Uri.DnsSafeHost;
if (info.Network != network.CLightningNetworkName)
{
throw new Exception($"Lightning node network {info.Network}, but expected is {network.CLightningNetworkName}");
throw new Exception($"Error while connecting to the API ({ex.Message})");
}
var blocksGap = Math.Abs(info.BlockHeight - summary.Status.ChainHeight);
@ -81,15 +72,15 @@ namespace BTCPayServer.Payments.Lightning
try
{
await TestConnection(address, port, cts.Token);
await TestConnection(info.Address, info.P2PPort, cts.Token);
}
catch (Exception ex)
{
throw new Exception($"Error while connecting to the lightning node via {address}:{port} ({ex.Message})");
throw new Exception($"Error while connecting to the lightning node via {info.Address}:{info.P2PPort} ({ex.Message})");
}
}
private static ChargeClient GetClient(LightningSupportedPaymentMethod supportedPaymentMethod, BTCPayNetwork network)
private static ILightningInvoiceClient GetClient(LightningSupportedPaymentMethod supportedPaymentMethod, BTCPayNetwork network)
{
return new ChargeClient(supportedPaymentMethod.GetLightningChargeUrl(true), network.NBitcoinNetwork);
}

View file

@ -6,14 +6,14 @@ using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Events;
using BTCPayServer.Logging;
using BTCPayServer.Payments.Lightning.CLightning;
using BTCPayServer.Services.Invoices;
using Microsoft.Extensions.Hosting;
using NBXplorer;
using BTCPayServer.Payments.Lightning.Charge;
namespace BTCPayServer.Payments.Lightning
{
public class ChargeListener : IHostedService
public class LightningListener : IHostedService
{
class ListenedInvoice
{
@ -28,7 +28,7 @@ namespace BTCPayServer.Payments.Lightning
EventAggregator _Aggregator;
InvoiceRepository _InvoiceRepository;
BTCPayNetworkProvider _NetworkProvider;
public ChargeListener(EventAggregator aggregator,
public LightningListener(EventAggregator aggregator,
InvoiceRepository invoiceRepository,
BTCPayNetworkProvider networkProvider)
{
@ -128,13 +128,13 @@ namespace BTCPayServer.Payments.Lightning
var session = await charge.Listen(_Cts.Token);
while (true)
{
var notification = await session.NextEvent(_Cts.Token);
var notification = await session.WaitInvoice(_Cts.Token);
ListenedInvoice listenedInvoice = GetListenedInvoice(notification.Id);
if (listenedInvoice == null)
continue;
if (notification.Id == listenedInvoice.PaymentMethodDetails.InvoiceId &&
notification.PaymentRequest == listenedInvoice.PaymentMethodDetails.BOLT11)
notification.BOLT11 == listenedInvoice.PaymentMethodDetails.BOLT11)
{
if (notification.Status == "paid" && notification.PaidAt.HasValue)
{
@ -161,18 +161,18 @@ namespace BTCPayServer.Payments.Lightning
Logs.PayServer.LogInformation($"{supportedPaymentMethod.CryptoCode} (Lightning): Stop listening {supportedPaymentMethod.GetLightningChargeUrl(false)}");
}
private async Task AddPayment(BTCPayNetwork network, ChargeInvoice notification, ListenedInvoice listenedInvoice)
private async Task AddPayment(BTCPayNetwork network, LightningInvoice notification, ListenedInvoice listenedInvoice)
{
var payment = await _InvoiceRepository.AddPayment(listenedInvoice.InvoiceId, notification.PaidAt.Value, new LightningLikePaymentData()
{
BOLT11 = notification.PaymentRequest,
Amount = notification.MilliSatoshi
BOLT11 = notification.BOLT11,
Amount = notification.Amount
}, network.CryptoCode, accounted: true);
if(payment != null)
_Aggregator.Publish(new InvoiceEvent(listenedInvoice.InvoiceId, 1002, "invoice_receivedPayment"));
}
private static ChargeClient GetChargeClient(LightningSupportedPaymentMethod supportedPaymentMethod, BTCPayNetwork network)
private static ILightningInvoiceClient GetChargeClient(LightningSupportedPaymentMethod supportedPaymentMethod, BTCPayNetwork network)
{
return new ChargeClient(supportedPaymentMethod.GetLightningChargeUrl(true), network.NBitcoinNetwork);
}