mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-01-18 21:32:27 +01:00
Can connect directly to CLightning via TCP or UNIX socket
This commit is contained in:
parent
392f3a16f1
commit
6d611d7d05
@ -1,5 +1,7 @@
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Hosting;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
@ -118,6 +120,7 @@ namespace BTCPayServer.Tests
|
||||
.Build();
|
||||
_Host.Start();
|
||||
InvoiceRepository = (InvoiceRepository)_Host.Services.GetService(typeof(InvoiceRepository));
|
||||
((LightningLikePaymentHandler)_Host.Services.GetService(typeof(IPaymentMethodHandler<LightningSupportedPaymentMethod>))).SkipP2PTest = !InContainer;
|
||||
}
|
||||
|
||||
public string HostName
|
||||
@ -127,6 +130,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
public InvoiceRepository InvoiceRepository { get; private set; }
|
||||
public Uri IntegratedLightning { get; internal set; }
|
||||
public bool InContainer { get; internal set; }
|
||||
|
||||
public T GetService<T>()
|
||||
{
|
||||
|
@ -57,7 +57,9 @@ namespace BTCPayServer.Tests
|
||||
LTCExplorerClient = new ExplorerClient(NetworkProvider.GetNetwork("LTC").NBXplorerNetwork, new Uri(GetEnvironment("TESTS_LTCNBXPLORERURL", "http://127.0.0.1:32838/")));
|
||||
|
||||
var btc = NetworkProvider.GetNetwork("BTC").NBitcoinNetwork;
|
||||
CustomerLightningD = new CLightningRPCClient(new Uri(GetEnvironment("TEST_CUSTOMERLIGHTNINGD", "http://127.0.0.1:30992/")), btc);
|
||||
CustomerLightningD = new CLightningRPCClient(new Uri(GetEnvironment("TEST_CUSTOMERLIGHTNINGD", "tcp://127.0.0.1:30992/")), btc);
|
||||
MerchantLightningD = new CLightningRPCClient(new Uri(GetEnvironment("TEST_MERCHANTLIGHTNINGD", "tcp://127.0.0.1:30993/")), btc);
|
||||
|
||||
MerchantCharge = new ChargeTester(this, "TEST_MERCHANTCHARGE", "http://api-token:foiewnccewuify@127.0.0.1:54938/", "merchant_lightningd", btc);
|
||||
|
||||
PayTester = new BTCPayServerTester(Path.Combine(_Directory, "pay"))
|
||||
@ -69,6 +71,7 @@ namespace BTCPayServer.Tests
|
||||
};
|
||||
PayTester.Port = int.Parse(GetEnvironment("TESTS_PORT", Utils.FreeTcpPort().ToString(CultureInfo.InvariantCulture)), CultureInfo.InvariantCulture);
|
||||
PayTester.HostName = GetEnvironment("TESTS_HOSTNAME", "127.0.0.1");
|
||||
PayTester.InContainer = bool.Parse(GetEnvironment("TESTS_INCONTAINER", "false"));
|
||||
PayTester.Start();
|
||||
}
|
||||
|
||||
@ -90,8 +93,10 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var skippedStates = new[] { "ONCHAIN", "CHANNELD_SHUTTING_DOWN", "CLOSINGD_SIGEXCHANGE", "CLOSINGD_COMPLETE", "FUNDING_SPEND_SEEN" };
|
||||
var channel = (await CustomerLightningD.ListPeersAsync())
|
||||
.SelectMany(p => p.Channels)
|
||||
.Where(c => !skippedStates.Contains(c.State ?? ""))
|
||||
.FirstOrDefault();
|
||||
switch (channel?.State)
|
||||
{
|
||||
@ -148,6 +153,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
|
||||
public CLightningRPCClient CustomerLightningD { get; set; }
|
||||
public CLightningRPCClient MerchantLightningD { get; private set; }
|
||||
public ChargeTester MerchantCharge { get; private set; }
|
||||
|
||||
internal string GetEnvironment(string variable, string defaultValue)
|
||||
|
@ -1,4 +1,5 @@
|
||||
using BTCPayServer.Controllers;
|
||||
using System.Linq;
|
||||
using BTCPayServer.Models.AccountViewModels;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
@ -11,6 +12,8 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
@ -112,19 +115,23 @@ namespace BTCPayServer.Tests
|
||||
get; set;
|
||||
}
|
||||
|
||||
public void RegisterLightningNode(string cryptoCode)
|
||||
public void RegisterLightningNode(string cryptoCode, LightningConnectionType connectionType)
|
||||
{
|
||||
RegisterLightningNodeAsync(cryptoCode).GetAwaiter().GetResult();
|
||||
RegisterLightningNodeAsync(cryptoCode, connectionType).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public async Task RegisterLightningNodeAsync(string cryptoCode)
|
||||
public async Task RegisterLightningNodeAsync(string cryptoCode, LightningConnectionType connectionType)
|
||||
{
|
||||
var storeController = parent.PayTester.GetController<StoresController>(UserId);
|
||||
await storeController.AddLightningNode(StoreId, new LightningNodeViewModel()
|
||||
{
|
||||
CryptoCurrency = "BTC",
|
||||
Url = parent.MerchantCharge.Client.Uri.AbsoluteUri
|
||||
Url = connectionType == LightningConnectionType.Charge ? parent.MerchantCharge.Client.Uri.AbsoluteUri :
|
||||
connectionType == LightningConnectionType.CLightning ? parent.MerchantLightningD.Address.AbsoluteUri
|
||||
: throw new NotSupportedException(connectionType.ToString())
|
||||
}, "save");
|
||||
if (storeController.ModelState.ErrorCount != 0)
|
||||
Assert.False(true, storeController.ModelState.FirstOrDefault().Value.Errors[0].ErrorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -324,6 +324,79 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanParseLightningURL()
|
||||
{
|
||||
LightningConnectionString conn = null;
|
||||
Assert.True(LightningConnectionString.TryParse("/test/a", out conn));
|
||||
Assert.Equal("unix://test/a", conn.ToString());
|
||||
Assert.Equal("unix://test/a", conn.ToUri(true).AbsoluteUri);
|
||||
Assert.Equal("unix://test/a", conn.ToUri(false).AbsoluteUri);
|
||||
Assert.Equal(LightningConnectionType.CLightning, conn.ConnectionType);
|
||||
|
||||
Assert.True(LightningConnectionString.TryParse("unix://test/a", out conn));
|
||||
Assert.Equal("unix://test/a", conn.ToString());
|
||||
Assert.Equal("unix://test/a", conn.ToUri(true).AbsoluteUri);
|
||||
Assert.Equal("unix://test/a", conn.ToUri(false).AbsoluteUri);
|
||||
Assert.Equal(LightningConnectionType.CLightning, conn.ConnectionType);
|
||||
|
||||
Assert.True(LightningConnectionString.TryParse("unix://test/a", out conn));
|
||||
Assert.Equal("unix://test/a", conn.ToString());
|
||||
Assert.Equal("unix://test/a", conn.ToUri(true).AbsoluteUri);
|
||||
Assert.Equal("unix://test/a", conn.ToUri(false).AbsoluteUri);
|
||||
Assert.Equal(LightningConnectionType.CLightning, conn.ConnectionType);
|
||||
|
||||
Assert.True(LightningConnectionString.TryParse("tcp://test/a", out conn));
|
||||
Assert.Equal("tcp://test/a", conn.ToString());
|
||||
Assert.Equal("tcp://test/a", conn.ToUri(true).AbsoluteUri);
|
||||
Assert.Equal("tcp://test/a", conn.ToUri(false).AbsoluteUri);
|
||||
Assert.Equal(LightningConnectionType.CLightning, conn.ConnectionType);
|
||||
|
||||
Assert.True(LightningConnectionString.TryParse("http://aaa:bbb@test/a", out conn));
|
||||
Assert.Equal("http://aaa:bbb@test/a", conn.ToString());
|
||||
Assert.Equal("http://aaa:bbb@test/a", conn.ToUri(true).AbsoluteUri);
|
||||
Assert.Equal("http://test/a", conn.ToUri(false).AbsoluteUri);
|
||||
Assert.Equal(LightningConnectionType.Charge, conn.ConnectionType);
|
||||
Assert.Equal("aaa", conn.Username);
|
||||
Assert.Equal("bbb", conn.Password);
|
||||
|
||||
Assert.False(LightningConnectionString.TryParse("lol://aaa:bbb@test/a", out conn));
|
||||
Assert.False(LightningConnectionString.TryParse("https://test/a", out conn));
|
||||
Assert.False(LightningConnectionString.TryParse("unix://dwewoi:dwdwqd@test/a", out conn));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanSendLightningPayment2()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
tester.Start();
|
||||
tester.PrepareLightning();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
user.RegisterLightningNode("BTC", LightningConnectionType.CLightning);
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
|
||||
var invoice = user.BitPay.CreateInvoice(new Invoice()
|
||||
{
|
||||
Price = 0.01,
|
||||
Currency = "USD",
|
||||
PosData = "posData",
|
||||
OrderId = "orderId",
|
||||
ItemDesc = "Some description"
|
||||
});
|
||||
|
||||
tester.SendLightningPayment(invoice);
|
||||
|
||||
Eventually(() =>
|
||||
{
|
||||
var localInvoice = user.BitPay.GetInvoice(invoice.Id);
|
||||
Assert.Equal("complete", localInvoice.Status);
|
||||
Assert.Equal("False", localInvoice.ExceptionStatus.ToString());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanSendLightningPayment()
|
||||
{
|
||||
@ -334,7 +407,7 @@ namespace BTCPayServer.Tests
|
||||
tester.PrepareLightning();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
user.RegisterLightningNode("BTC");
|
||||
user.RegisterLightningNode("BTC", LightningConnectionType.Charge);
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
|
||||
var invoice = user.BitPay.CreateInvoice(new Invoice()
|
||||
|
@ -17,14 +17,19 @@ services:
|
||||
TESTS_POSTGRES: User ID=postgres;Host=postgres;Port=5432;Database=btcpayserver
|
||||
TESTS_PORT: 80
|
||||
TESTS_HOSTNAME: tests
|
||||
TEST_CUSTOMERLIGHTNINGD: http://customer_lightningd:9835/
|
||||
TEST_MERCHANTLIGHTNINGD: "/etc/merchant_lightningd_datadir/lightning-rpc"
|
||||
TEST_CUSTOMERLIGHTNINGD: "/etc/customer_lightningd_datadir/lightning-rpc"
|
||||
TEST_MERCHANTCHARGE: http://api-token:foiewnccewuify@lightning-charged:9112/
|
||||
TESTS_INCONTAINER: "true"
|
||||
expose:
|
||||
- "80"
|
||||
links:
|
||||
- dev
|
||||
extra_hosts:
|
||||
- "tests:127.0.0.1"
|
||||
volumes:
|
||||
- "customer_lightningd_datadir:/etc/customer_lightningd_datadir"
|
||||
- "merchant_lightningd_datadir:/etc/merchant_lightningd_datadir"
|
||||
|
||||
# The dev container is not actually used, it is just handy to run `docker-compose up dev` to start all services
|
||||
dev:
|
||||
@ -90,6 +95,7 @@ services:
|
||||
bitcoin-datadir=/etc/bitcoin
|
||||
bitcoin-rpcconnect=bitcoind
|
||||
network=regtest
|
||||
ipaddr=customer_lightningd
|
||||
log-level=debug
|
||||
ports:
|
||||
- "30992:9835" # api port
|
||||
@ -128,6 +134,7 @@ services:
|
||||
LIGHTNINGD_OPT: |
|
||||
bitcoin-datadir=/etc/bitcoin
|
||||
bitcoin-rpcconnect=bitcoind
|
||||
ipaddr=merchant_lightningd
|
||||
network=regtest
|
||||
log-level=debug
|
||||
ports:
|
||||
|
1
BTCPayServer.Tests/docker-merchant-lightning-cli.ps1
Executable file
1
BTCPayServer.Tests/docker-merchant-lightning-cli.ps1
Executable file
@ -0,0 +1 @@
|
||||
docker exec -ti btcpayservertests_merchant_lightningd_1 lightning-cli $args
|
3
BTCPayServer.Tests/docker-merchant-lightning-cli.sh
Executable file
3
BTCPayServer.Tests/docker-merchant-lightning-cli.sh
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
docker exec -ti btcpayservertests_merchant_lightningd_1 lightning-cli "$@"
|
@ -8,6 +8,7 @@ using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Lightning.CLightning;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using System.Net;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
@ -23,7 +24,7 @@ namespace BTCPayServer.Controllers
|
||||
return NotFound();
|
||||
LightningNodeViewModel vm = new LightningNodeViewModel();
|
||||
vm.SetCryptoCurrencies(_NetworkProvider, selectedCrypto);
|
||||
vm.InternalLightningNode = GetInternalLightningNodeIfAuthorized();
|
||||
vm.InternalLightningNode = CanUseInternalLightning() ? _BtcpayServerOptions.InternalLightningNode.AbsoluteUri : null;
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
@ -36,7 +37,7 @@ namespace BTCPayServer.Controllers
|
||||
return NotFound();
|
||||
var network = vm.CryptoCurrency == null ? null : _ExplorerProvider.GetNetwork(vm.CryptoCurrency);
|
||||
vm.SetCryptoCurrencies(_NetworkProvider, vm.CryptoCurrency);
|
||||
vm.InternalLightningNode = GetInternalLightningNodeIfAuthorized();
|
||||
vm.InternalLightningNode = CanUseInternalLightning() ? _BtcpayServerOptions.InternalLightningNode.AbsoluteUri : null;
|
||||
if (network == null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.CryptoCurrency), "Invalid network");
|
||||
@ -47,41 +48,39 @@ namespace BTCPayServer.Controllers
|
||||
Payments.Lightning.LightningSupportedPaymentMethod paymentMethod = null;
|
||||
if (!string.IsNullOrEmpty(vm.Url))
|
||||
{
|
||||
Uri uri;
|
||||
if (!Uri.TryCreate(vm.Url, UriKind.Absolute, out uri))
|
||||
if(!LightningConnectionString.TryParse(vm.Url, out var connectionString, out var error))
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.Url), "Invalid URL");
|
||||
ModelState.AddModelError(nameof(vm.Url), $"Invalid URL ({error})");
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
var domain = GetDomain(uri.AbsoluteUri);
|
||||
if (uri.Scheme != "https" && domain != "127.0.0.1" && domain != "localhost")
|
||||
var internalDomain = _BtcpayServerOptions.InternalLightningNode.DnsSafeHost;
|
||||
bool isLocal = (internalDomain == "127.0.0.1" || internalDomain == "localhost");
|
||||
|
||||
bool isInternalNode = connectionString.ConnectionType == LightningConnectionType.CLightning ||
|
||||
connectionString.BaseUri.DnsSafeHost == internalDomain ||
|
||||
isLocal;
|
||||
|
||||
if (connectionString.BaseUri.Scheme == "http" && !isLocal)
|
||||
{
|
||||
var internalNode = GetInternalLightningNodeIfAuthorized();
|
||||
if (internalNode == null || GetDomain(internalNode) != domain)
|
||||
if (!isInternalNode || (isInternalNode && !CanUseInternalLightning()))
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.Url), "The url must be HTTPS");
|
||||
return View(vm);
|
||||
}
|
||||
}
|
||||
|
||||
if (!CanUseInternalLightning() && GetDomain(_BtcpayServerOptions.InternalLightningNode.AbsoluteUri) == GetDomain(uri.AbsoluteUri))
|
||||
if (isInternalNode && !CanUseInternalLightning())
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.Url), "Unauthorized url");
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(uri.UserInfo) || uri.UserInfo.Split(':').Length != 2)
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.Url), "The url is missing user and password");
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
paymentMethod = new Payments.Lightning.LightningSupportedPaymentMethod()
|
||||
{
|
||||
CryptoCode = paymentMethodId.CryptoCode
|
||||
};
|
||||
paymentMethod.SetLightningChargeUrl(uri);
|
||||
paymentMethod.SetLightningUrl(connectionString);
|
||||
}
|
||||
if (command == "save")
|
||||
{
|
||||
@ -112,24 +111,9 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
private string GetInternalLightningNodeIfAuthorized()
|
||||
{
|
||||
if (_BtcpayServerOptions.InternalLightningNode != null &&
|
||||
CanUseInternalLightning())
|
||||
{
|
||||
return _BtcpayServerOptions.InternalLightningNode.AbsoluteUri;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool CanUseInternalLightning()
|
||||
{
|
||||
return (_BTCPayEnv.IsDevelopping || User.IsInRole(Roles.ServerAdmin));
|
||||
}
|
||||
|
||||
string GetDomain(string uri)
|
||||
{
|
||||
return new UriBuilder(uri).Host;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -246,7 +246,7 @@ namespace BTCPayServer.Controllers
|
||||
vm.LightningNodes.Add(new StoreViewModel.LightningNode()
|
||||
{
|
||||
CryptoCode = lightning.CryptoCode,
|
||||
Address = lightning.GetLightningChargeUrl(false).AbsoluteUri
|
||||
Address = lightning.GetLightningUrl().BaseUri.AbsoluteUri
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -5,8 +5,10 @@ using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Payments.Lightning.Charge;
|
||||
using Mono.Unix;
|
||||
using NBitcoin;
|
||||
using NBitcoin.RPC;
|
||||
using Newtonsoft.Json;
|
||||
@ -14,7 +16,14 @@ using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Payments.Lightning.CLightning
|
||||
{
|
||||
public class CLightningRPCClient
|
||||
public class LightningRPCException : Exception
|
||||
{
|
||||
public LightningRPCException(string message) : base(message)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
public class CLightningRPCClient : ILightningInvoiceClient, ILightningListenInvoiceSession
|
||||
{
|
||||
public Network Network { get; private set; }
|
||||
public Uri Address { get; private set; }
|
||||
@ -25,13 +34,17 @@ namespace BTCPayServer.Payments.Lightning.CLightning
|
||||
throw new ArgumentNullException(nameof(address));
|
||||
if (network == null)
|
||||
throw new ArgumentNullException(nameof(network));
|
||||
if(address.Scheme == "file")
|
||||
{
|
||||
address = new UriBuilder(address) { Scheme = "unix" }.Uri;
|
||||
}
|
||||
Address = address;
|
||||
Network = network;
|
||||
}
|
||||
|
||||
public Task<GetInfoResponse> GetInfoAsync()
|
||||
public Task<GetInfoResponse> GetInfoAsync(CancellationToken cancellation = default(CancellationToken))
|
||||
{
|
||||
return SendCommandAsync<GetInfoResponse>("getinfo");
|
||||
return SendCommandAsync<GetInfoResponse>("getinfo", cancellation: cancellation);
|
||||
}
|
||||
|
||||
public Task SendAsync(string bolt11)
|
||||
@ -60,18 +73,11 @@ namespace BTCPayServer.Payments.Lightning.CLightning
|
||||
}
|
||||
|
||||
static Encoding UTF8 = new UTF8Encoding(false);
|
||||
private async Task<T> SendCommandAsync<T>(string command, object[] parameters = null, bool noReturn = false, bool isArray = false)
|
||||
private async Task<T> SendCommandAsync<T>(string command, object[] parameters = null, bool noReturn = false, bool isArray = false, CancellationToken cancellation = default(CancellationToken))
|
||||
{
|
||||
parameters = parameters ?? Array.Empty<string>();
|
||||
var domain = Address.DnsSafeHost;
|
||||
if (!IPAddress.TryParse(domain, out IPAddress address))
|
||||
using (Socket socket = await Connect())
|
||||
{
|
||||
address = (await Dns.GetHostAddressesAsync(domain)).FirstOrDefault();
|
||||
if (address == null)
|
||||
throw new Exception("Host not found");
|
||||
}
|
||||
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
||||
await socket.ConnectAsync(new IPEndPoint(address, Address.Port));
|
||||
using (var networkStream = new NetworkStream(socket))
|
||||
{
|
||||
using (var textWriter = new StreamWriter(networkStream, UTF8, 1024 * 10, true))
|
||||
@ -82,21 +88,26 @@ namespace BTCPayServer.Payments.Lightning.CLightning
|
||||
req.Add("id", 0);
|
||||
req.Add("method", command);
|
||||
req.Add("params", new JArray(parameters));
|
||||
await req.WriteToAsync(jsonWriter);
|
||||
await jsonWriter.FlushAsync();
|
||||
await req.WriteToAsync(jsonWriter, cancellation);
|
||||
await jsonWriter.FlushAsync(cancellation);
|
||||
}
|
||||
await textWriter.FlushAsync();
|
||||
}
|
||||
await networkStream.FlushAsync();
|
||||
await networkStream.FlushAsync(cancellation);
|
||||
using (var textReader = new StreamReader(networkStream, UTF8, false, 1024 * 10, true))
|
||||
{
|
||||
using (var jsonReader = new JsonTextReader(textReader))
|
||||
{
|
||||
var result = await JObject.LoadAsync(jsonReader);
|
||||
var resultAsync = JObject.LoadAsync(jsonReader, cancellation);
|
||||
|
||||
// without this hack resultAsync is blocking even if cancellation happen
|
||||
using (cancellation.Register(() => { socket.Dispose(); }))
|
||||
{
|
||||
var result = await resultAsync;
|
||||
var error = result.Property("error");
|
||||
if (error != null)
|
||||
{
|
||||
throw new Exception(error.Value.ToString());
|
||||
throw new LightningRPCException(error.Value["message"].Value<string>());
|
||||
}
|
||||
if (noReturn)
|
||||
return default(T);
|
||||
@ -109,11 +120,111 @@ namespace BTCPayServer.Payments.Lightning.CLightning
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<Socket> Connect()
|
||||
{
|
||||
Socket socket = null;
|
||||
EndPoint endpoint = null;
|
||||
if (Address.Scheme == "tcp" || Address.Scheme == "tcp")
|
||||
{
|
||||
var domain = Address.DnsSafeHost;
|
||||
if (!IPAddress.TryParse(domain, out IPAddress address))
|
||||
{
|
||||
address = (await Dns.GetHostAddressesAsync(domain)).FirstOrDefault();
|
||||
if (address == null)
|
||||
throw new Exception("Host not found");
|
||||
}
|
||||
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
||||
endpoint = new IPEndPoint(address, Address.Port);
|
||||
}
|
||||
else if (Address.Scheme == "unix")
|
||||
{
|
||||
var path = Address.AbsoluteUri.Remove(0, "unix:".Length);
|
||||
if (!path.StartsWith('/'))
|
||||
path = "/" + path;
|
||||
while (path.Length >= 2 && (path[0] != '/' || path[1] == '/'))
|
||||
{
|
||||
path = path.Remove(0, 1);
|
||||
}
|
||||
if (path.Length < 2)
|
||||
throw new FormatException("Invalid unix url");
|
||||
socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.IP);
|
||||
endpoint = new UnixEndPoint(path);
|
||||
}
|
||||
else
|
||||
throw new NotSupportedException($"Protocol {Address.Scheme} for clightning not supported");
|
||||
|
||||
await socket.ConnectAsync(endpoint);
|
||||
return socket;
|
||||
}
|
||||
|
||||
public async Task<BitcoinAddress> NewAddressAsync()
|
||||
{
|
||||
var obj = await SendCommandAsync<JObject>("newaddr");
|
||||
return BitcoinAddress.Create(obj.Property("address").Value.Value<string>(), Network);
|
||||
}
|
||||
|
||||
async Task<LightningInvoice> ILightningInvoiceClient.GetInvoice(string invoiceId, CancellationToken cancellation)
|
||||
{
|
||||
var invoices = await SendCommandAsync<ChargeInvoice[]>("listinvoices", new[] { invoiceId }, false, true, cancellation);
|
||||
if (invoices.Length == 0)
|
||||
return null;
|
||||
return ChargeClient.ToLightningInvoice(invoices[0]);
|
||||
}
|
||||
|
||||
static NBitcoin.DataEncoders.DataEncoder InvoiceIdEncoder = NBitcoin.DataEncoders.Encoders.Base58;
|
||||
async Task<LightningInvoice> ILightningInvoiceClient.CreateInvoice(LightMoney amount, TimeSpan expiry, CancellationToken cancellation)
|
||||
{
|
||||
var id = InvoiceIdEncoder.EncodeData(RandomUtils.GetBytes(20));
|
||||
var invoice = await SendCommandAsync<CreateInvoiceResponse>("invoice", new object[] { amount.MilliSatoshi, id, "" }, cancellation: cancellation);
|
||||
invoice.Label = id;
|
||||
invoice.MilliSatoshi = amount;
|
||||
invoice.Status = "unpaid";
|
||||
return ToLightningInvoice(invoice);
|
||||
}
|
||||
|
||||
private static LightningInvoice ToLightningInvoice(CreateInvoiceResponse invoice)
|
||||
{
|
||||
return new LightningInvoice()
|
||||
{
|
||||
Id = invoice.Label,
|
||||
Amount = invoice.MilliSatoshi,
|
||||
BOLT11 = invoice.BOLT11,
|
||||
Status = invoice.Status,
|
||||
PaidAt = invoice.PaidAt
|
||||
};
|
||||
}
|
||||
|
||||
Task<ILightningListenInvoiceSession> ILightningInvoiceClient.Listen(CancellationToken cancellation)
|
||||
{
|
||||
return Task.FromResult<ILightningListenInvoiceSession>(this);
|
||||
}
|
||||
long lastInvoiceIndex = 99999999999;
|
||||
async Task<LightningInvoice> ILightningListenInvoiceSession.WaitInvoice(CancellationToken cancellation)
|
||||
{
|
||||
var chargeInvoice = await SendCommandAsync<CreateInvoiceResponse>("waitanyinvoice", new object[] { lastInvoiceIndex }, cancellation: cancellation);
|
||||
lastInvoiceIndex = chargeInvoice.PayIndex.Value;
|
||||
return ToLightningInvoice(chargeInvoice);
|
||||
}
|
||||
|
||||
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;
|
||||
return new LightningNodeInformation()
|
||||
{
|
||||
P2PPort = port,
|
||||
Address = address,
|
||||
BlockHeight = info.BlockHeight
|
||||
};
|
||||
}
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Payments.Lightning.CLightning
|
||||
{
|
||||
public class CreateInvoiceResponse
|
||||
{
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.UInt256JsonConverter))]
|
||||
[JsonProperty("payment_hash")]
|
||||
public uint256 PaymentHash { get; set; }
|
||||
|
||||
[JsonProperty("msatoshi")]
|
||||
[JsonConverter(typeof(JsonConverters.LightMoneyJsonConverter))]
|
||||
public LightMoney MilliSatoshi { get; set; }
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
|
||||
[JsonProperty("expiry_time")]
|
||||
public DateTimeOffset ExpiryTime { get; set; }
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
|
||||
[JsonProperty("expires_at")]
|
||||
public DateTimeOffset ExpiryAt { get; set; }
|
||||
[JsonProperty("bolt11")]
|
||||
public string BOLT11 { get; set; }
|
||||
[JsonProperty("pay_index")]
|
||||
public int? PayIndex { get; set; }
|
||||
public string Label { get; set; }
|
||||
public string Status { get; set; }
|
||||
[JsonProperty("paid_at")]
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
|
||||
public DateTimeOffset? PaidAt { get; set; }
|
||||
}
|
||||
}
|
140
BTCPayServer/Payments/Lightning/CLightning/UnixEndPoint.cs
Normal file
140
BTCPayServer/Payments/Lightning/CLightning/UnixEndPoint.cs
Normal file
@ -0,0 +1,140 @@
|
||||
//
|
||||
// Mono.Unix.UnixEndPoint: EndPoint derived class for AF_UNIX family sockets.
|
||||
//
|
||||
// Authors:
|
||||
// Gonzalo Paniagua Javier (gonzalo@ximian.com)
|
||||
//
|
||||
// (C) 2003 Ximian, Inc (http://www.ximian.com)
|
||||
//
|
||||
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
|
||||
namespace Mono.Unix
|
||||
{
|
||||
[Serializable]
|
||||
public class UnixEndPoint : EndPoint
|
||||
{
|
||||
string filename;
|
||||
|
||||
public UnixEndPoint(string filename)
|
||||
{
|
||||
if (filename == null)
|
||||
throw new ArgumentNullException("filename");
|
||||
|
||||
if (filename.Length == 0)
|
||||
throw new ArgumentException("Cannot be empty.", "filename");
|
||||
this.filename = filename;
|
||||
}
|
||||
|
||||
public string Filename
|
||||
{
|
||||
get
|
||||
{
|
||||
return (filename);
|
||||
}
|
||||
set
|
||||
{
|
||||
filename = value;
|
||||
}
|
||||
}
|
||||
|
||||
public override AddressFamily AddressFamily
|
||||
{
|
||||
get { return AddressFamily.Unix; }
|
||||
}
|
||||
|
||||
public override EndPoint Create(SocketAddress socketAddress)
|
||||
{
|
||||
/*
|
||||
* Should also check this
|
||||
*
|
||||
int addr = (int) AddressFamily.Unix;
|
||||
if (socketAddress [0] != (addr & 0xFF))
|
||||
throw new ArgumentException ("socketAddress is not a unix socket address.");
|
||||
|
||||
if (socketAddress [1] != ((addr & 0xFF00) >> 8))
|
||||
throw new ArgumentException ("socketAddress is not a unix socket address.");
|
||||
*/
|
||||
|
||||
if (socketAddress.Size == 2)
|
||||
{
|
||||
// Empty filename.
|
||||
// Probably from RemoteEndPoint which on linux does not return the file name.
|
||||
UnixEndPoint uep = new UnixEndPoint("a");
|
||||
uep.filename = "";
|
||||
return uep;
|
||||
}
|
||||
int size = socketAddress.Size - 2;
|
||||
byte[] bytes = new byte[size];
|
||||
for (int i = 0; i < bytes.Length; i++)
|
||||
{
|
||||
bytes[i] = socketAddress[i + 2];
|
||||
// There may be junk after the null terminator, so ignore it all.
|
||||
if (bytes[i] == 0)
|
||||
{
|
||||
size = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
string name = Encoding.Default.GetString(bytes, 0, size);
|
||||
return new UnixEndPoint(name);
|
||||
}
|
||||
|
||||
public override SocketAddress Serialize()
|
||||
{
|
||||
byte[] bytes = Encoding.Default.GetBytes(filename);
|
||||
SocketAddress sa = new SocketAddress(AddressFamily, 2 + bytes.Length + 1);
|
||||
// sa [0] -> family low byte, sa [1] -> family high byte
|
||||
for (int i = 0; i < bytes.Length; i++)
|
||||
sa[2 + i] = bytes[i];
|
||||
|
||||
//NULL suffix for non-abstract path
|
||||
sa[2 + bytes.Length] = 0;
|
||||
|
||||
return sa;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return (filename);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return filename.GetHashCode(StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
public override bool Equals(object o)
|
||||
{
|
||||
UnixEndPoint other = o as UnixEndPoint;
|
||||
if (other == null)
|
||||
return false;
|
||||
|
||||
return (other.filename == filename);
|
||||
}
|
||||
}
|
||||
}
|
@ -142,7 +142,7 @@ namespace BTCPayServer.Payments.Lightning.Charge
|
||||
{
|
||||
return new LightningInvoice()
|
||||
{
|
||||
Id = invoice.Id,
|
||||
Id = invoice.Id ?? invoice.Label,
|
||||
Amount = invoice.MilliSatoshi,
|
||||
BOLT11 = invoice.PaymentRequest,
|
||||
PaidAt = invoice.PaidAt,
|
||||
@ -161,7 +161,6 @@ namespace BTCPayServer.Payments.Lightning.Charge
|
||||
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,
|
||||
|
@ -27,6 +27,7 @@ namespace BTCPayServer.Payments.Lightning.Charge
|
||||
|
||||
[JsonProperty("payreq")]
|
||||
public string PaymentRequest { get; set; }
|
||||
public string Label { get; set; }
|
||||
}
|
||||
public class ChargeSession : ILightningListenInvoiceSession
|
||||
{
|
||||
|
@ -35,6 +35,6 @@ namespace BTCPayServer.Payments.Lightning
|
||||
|
||||
public interface ILightningListenInvoiceSession : IDisposable
|
||||
{
|
||||
Task<LightningInvoice> WaitInvoice(CancellationToken token);
|
||||
Task<LightningInvoice> WaitInvoice(CancellationToken cancellation);
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Payments.Lightning.Charge;
|
||||
using BTCPayServer.Payments.Lightning.CLightning;
|
||||
|
||||
namespace BTCPayServer.Payments.Lightning
|
||||
{
|
||||
@ -10,7 +11,17 @@ namespace BTCPayServer.Payments.Lightning
|
||||
{
|
||||
public ILightningInvoiceClient CreateClient(LightningSupportedPaymentMethod supportedPaymentMethod, BTCPayNetwork network)
|
||||
{
|
||||
return new ChargeClient(supportedPaymentMethod.GetLightningChargeUrl(true), network.NBitcoinNetwork);
|
||||
var uri = supportedPaymentMethod.GetLightningUrl();
|
||||
if (uri.ConnectionType == LightningConnectionType.Charge)
|
||||
{
|
||||
return new ChargeClient(uri.ToUri(true), network.NBitcoinNetwork);
|
||||
}
|
||||
else if (uri.ConnectionType == LightningConnectionType.CLightning)
|
||||
{
|
||||
return new CLightningRPCClient(uri.ToUri(false), network.NBitcoinNetwork);
|
||||
}
|
||||
else
|
||||
throw new NotSupportedException($"Unsupported connection string for lightning server ({uri.ConnectionType})");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
110
BTCPayServer/Payments/Lightning/LightningConnectionString.cs
Normal file
110
BTCPayServer/Payments/Lightning/LightningConnectionString.cs
Normal file
@ -0,0 +1,110 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Payments.Lightning
|
||||
{
|
||||
public enum LightningConnectionType
|
||||
{
|
||||
Charge,
|
||||
CLightning
|
||||
}
|
||||
public class LightningConnectionString
|
||||
{
|
||||
public static bool TryParse(string str, out LightningConnectionString connectionString)
|
||||
{
|
||||
return TryParse(str, out connectionString, out var error);
|
||||
}
|
||||
public static bool TryParse(string str, out LightningConnectionString connectionString, out string error)
|
||||
{
|
||||
if (str == null)
|
||||
throw new ArgumentNullException(nameof(str));
|
||||
if (str.StartsWith('/'))
|
||||
str = "unix:" + str;
|
||||
var result = new LightningConnectionString();
|
||||
connectionString = null;
|
||||
error = null;
|
||||
|
||||
Uri uri;
|
||||
if (!System.Uri.TryCreate(str, UriKind.Absolute, out uri))
|
||||
{
|
||||
error = "Invalid URL";
|
||||
return false;
|
||||
}
|
||||
|
||||
var supportedDomains = new string[] { "unix", "tcp", "http", "https" };
|
||||
if (!supportedDomains.Contains(uri.Scheme))
|
||||
{
|
||||
var protocols = String.Join(",", supportedDomains);
|
||||
error = $"The url support the following protocols {protocols}";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (uri.Scheme == "unix")
|
||||
{
|
||||
str = uri.AbsoluteUri.Substring("unix:".Length);
|
||||
while (str.Length >= 1 && str[0] == '/')
|
||||
{
|
||||
str = str.Substring(1);
|
||||
}
|
||||
uri = new Uri("unix://" + str, UriKind.Absolute);
|
||||
}
|
||||
|
||||
if (uri.Scheme == "http" || uri.Scheme == "https")
|
||||
{
|
||||
var parts = uri.UserInfo.Split(':');
|
||||
if (string.IsNullOrEmpty(uri.UserInfo) || parts.Length != 2)
|
||||
{
|
||||
error = "The url is missing user and password";
|
||||
return false;
|
||||
}
|
||||
result.Username = parts[0];
|
||||
result.Password = parts[1];
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(uri.UserInfo))
|
||||
{
|
||||
error = "The url should not have user information";
|
||||
return false;
|
||||
}
|
||||
result.BaseUri = new UriBuilder(uri) { UserName = "", Password = "" }.Uri;
|
||||
connectionString = result;
|
||||
return true;
|
||||
}
|
||||
|
||||
public LightningConnectionString()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public string Username { get; set; }
|
||||
public string Password { get; set; }
|
||||
public Uri BaseUri { get; set; }
|
||||
|
||||
public LightningConnectionType ConnectionType
|
||||
{
|
||||
get
|
||||
{
|
||||
return BaseUri.Scheme == "http" || BaseUri.Scheme == "https" ? LightningConnectionType.Charge
|
||||
: LightningConnectionType.CLightning;
|
||||
}
|
||||
}
|
||||
|
||||
public Uri ToUri(bool withCredentials)
|
||||
{
|
||||
if (withCredentials)
|
||||
{
|
||||
return new UriBuilder(BaseUri) { UserName = Username ?? "", Password = Password ?? "" }.Uri;
|
||||
}
|
||||
else
|
||||
{
|
||||
return BaseUri;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return ToUri(true).AbsoluteUri;
|
||||
}
|
||||
}
|
||||
}
|
@ -49,18 +49,21 @@ namespace BTCPayServer.Payments.Lightning
|
||||
catch { return false; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used for testing
|
||||
/// </summary>
|
||||
public bool SkipP2PTest { get; set; }
|
||||
|
||||
public async Task Test(LightningSupportedPaymentMethod supportedPaymentMethod, BTCPayNetwork network)
|
||||
{
|
||||
if (!_Dashboard.IsFullySynched(network.CryptoCode, out var summary))
|
||||
throw new Exception($"Full node not available");
|
||||
|
||||
|
||||
var cts = new CancellationTokenSource(5000);
|
||||
var client = _LightningClientFactory.CreateClient(supportedPaymentMethod, network);
|
||||
LightningNodeInformation info = null;
|
||||
try
|
||||
{
|
||||
|
||||
info = await client.GetInfo(cts.Token);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -68,6 +71,11 @@ namespace BTCPayServer.Payments.Lightning
|
||||
throw new Exception($"Error while connecting to the API ({ex.Message})");
|
||||
}
|
||||
|
||||
if(info.Address == null)
|
||||
{
|
||||
throw new Exception($"No lightning node public address has been configured");
|
||||
}
|
||||
|
||||
var blocksGap = Math.Abs(info.BlockHeight - summary.Status.ChainHeight);
|
||||
if (blocksGap > 10)
|
||||
{
|
||||
@ -76,6 +84,7 @@ namespace BTCPayServer.Payments.Lightning
|
||||
|
||||
try
|
||||
{
|
||||
if(!SkipP2PTest)
|
||||
await TestConnection(info.Address, info.P2PPort, cts.Token);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -79,7 +79,7 @@ namespace BTCPayServer.Payments.Lightning
|
||||
|
||||
var listenedInvoice = new ListenedInvoice()
|
||||
{
|
||||
Uri = lightningSupportedMethod.GetLightningChargeUrl(false).AbsoluteUri,
|
||||
Uri = lightningSupportedMethod.GetLightningUrl().BaseUri.AbsoluteUri,
|
||||
PaymentMethodDetails = lightningMethod,
|
||||
SupportedPaymentMethod = lightningSupportedMethod,
|
||||
PaymentMethod = paymentMethod,
|
||||
@ -125,7 +125,7 @@ namespace BTCPayServer.Payments.Lightning
|
||||
{
|
||||
try
|
||||
{
|
||||
Logs.PayServer.LogInformation($"{supportedPaymentMethod.CryptoCode} (Lightning): Start listening {supportedPaymentMethod.GetLightningChargeUrl(false)}");
|
||||
Logs.PayServer.LogInformation($"{supportedPaymentMethod.CryptoCode} (Lightning): Start listening {supportedPaymentMethod.GetLightningUrl().BaseUri}");
|
||||
var charge = _LightningClientFactory.CreateClient(supportedPaymentMethod, network);
|
||||
var session = await charge.Listen(_Cts.Token);
|
||||
while (true)
|
||||
@ -134,7 +134,6 @@ namespace BTCPayServer.Payments.Lightning
|
||||
ListenedInvoice listenedInvoice = GetListenedInvoice(notification.Id);
|
||||
if (listenedInvoice == null)
|
||||
continue;
|
||||
|
||||
if (notification.Id == listenedInvoice.PaymentMethodDetails.InvoiceId &&
|
||||
notification.BOLT11 == listenedInvoice.PaymentMethodDetails.BOLT11)
|
||||
{
|
||||
@ -157,10 +156,10 @@ namespace BTCPayServer.Payments.Lightning
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logs.PayServer.LogError(ex, $"{supportedPaymentMethod.CryptoCode} (Lightning): Error while contacting {supportedPaymentMethod.GetLightningChargeUrl(false)}");
|
||||
DoneListening(supportedPaymentMethod.GetLightningChargeUrl(false));
|
||||
Logs.PayServer.LogError(ex, $"{supportedPaymentMethod.CryptoCode} (Lightning): Error while contacting {supportedPaymentMethod.GetLightningUrl().BaseUri}");
|
||||
DoneListening(supportedPaymentMethod.GetLightningUrl());
|
||||
}
|
||||
Logs.PayServer.LogInformation($"{supportedPaymentMethod.CryptoCode} (Lightning): Stop listening {supportedPaymentMethod.GetLightningChargeUrl(false)}");
|
||||
Logs.PayServer.LogInformation($"{supportedPaymentMethod.CryptoCode} (Lightning): Stop listening {supportedPaymentMethod.GetLightningUrl().BaseUri}");
|
||||
}
|
||||
|
||||
private async Task AddPayment(BTCPayNetwork network, LightningInvoice notification, ListenedInvoice listenedInvoice)
|
||||
@ -204,8 +203,9 @@ namespace BTCPayServer.Payments.Lightning
|
||||
/// Stop listening all invoices on this server
|
||||
/// </summary>
|
||||
/// <param name="uri"></param>
|
||||
private void DoneListening(Uri uri)
|
||||
private void DoneListening(LightningConnectionString connectionString)
|
||||
{
|
||||
var uri = connectionString.BaseUri;
|
||||
lock (_ListenedInvoiceByChargeInvoiceId)
|
||||
{
|
||||
foreach (var listenedInvoice in _ListenedInvoiceByLightningUrl[uri.AbsoluteUri])
|
||||
|
@ -8,41 +8,36 @@ namespace BTCPayServer.Payments.Lightning
|
||||
public class LightningSupportedPaymentMethod : ISupportedPaymentMethod
|
||||
{
|
||||
public string CryptoCode { get; set; }
|
||||
[Obsolete("Use Get/SetLightningChargeUrl")]
|
||||
[Obsolete("Use Get/SetLightningUrl")]
|
||||
public string LightningChargeUrl { get; set; }
|
||||
|
||||
public Uri GetLightningChargeUrl(bool withCredentials)
|
||||
public LightningConnectionString GetLightningUrl()
|
||||
{
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
UriBuilder uri = new UriBuilder(LightningChargeUrl);
|
||||
if (withCredentials)
|
||||
var fullUri = new UriBuilder(LightningChargeUrl) { UserName = Username, Password = Password }.Uri.AbsoluteUri;
|
||||
if(!LightningConnectionString.TryParse(fullUri, out var connectionString, out var error))
|
||||
{
|
||||
uri.UserName = Username;
|
||||
uri.Password = Password;
|
||||
throw new FormatException(error);
|
||||
}
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
return uri.Uri;
|
||||
}
|
||||
|
||||
public void SetLightningChargeUrl(Uri uri)
|
||||
{
|
||||
if (uri == null)
|
||||
throw new ArgumentNullException(nameof(uri));
|
||||
if (string.IsNullOrEmpty(uri.UserInfo))
|
||||
throw new ArgumentException(paramName: nameof(uri), message: "Uri should have credential information");
|
||||
var splitted = uri.UserInfo.Split(':');
|
||||
if (splitted.Length != 2)
|
||||
throw new ArgumentException(paramName: nameof(uri), message: "Uri should have credential information");
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
Username = splitted[0];
|
||||
Password = splitted[1];
|
||||
LightningChargeUrl = new UriBuilder(uri) { UserName = "", Password = "" }.Uri.AbsoluteUri;
|
||||
return connectionString;
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
}
|
||||
|
||||
[Obsolete("Use Get/SetLightningChargeUrl")]
|
||||
public void SetLightningUrl(LightningConnectionString connectionString)
|
||||
{
|
||||
if (connectionString == null)
|
||||
throw new ArgumentNullException(nameof(connectionString));
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
Username = connectionString.Username;
|
||||
Password = connectionString.Password;
|
||||
LightningChargeUrl = connectionString.BaseUri.AbsoluteUri;
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
}
|
||||
|
||||
[Obsolete("Use Get/SetLightningUrl")]
|
||||
public string Username { get; set; }
|
||||
[Obsolete("Use Get/SetLightningChargeUrl")]
|
||||
[Obsolete("Use Get/SetLightningUrl")]
|
||||
public string Password { get; set; }
|
||||
public PaymentMethodId PaymentId => new PaymentMethodId(CryptoCode, PaymentTypes.LightningLike);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user