mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-17 22:14:26 +01:00
Create a new format for LightningConnectionString
This commit is contained in:
@@ -128,7 +128,7 @@ namespace BTCPayServer.Tests
|
|||||||
var storeController = this.GetController<StoresController>();
|
var storeController = this.GetController<StoresController>();
|
||||||
await storeController.AddLightningNode(StoreId, new LightningNodeViewModel()
|
await storeController.AddLightningNode(StoreId, new LightningNodeViewModel()
|
||||||
{
|
{
|
||||||
Url = connectionType == LightningConnectionType.Charge ? parent.MerchantCharge.Client.Uri.AbsoluteUri :
|
ConnectionString = connectionType == LightningConnectionType.Charge ? parent.MerchantCharge.Client.Uri.AbsoluteUri :
|
||||||
connectionType == LightningConnectionType.CLightning ? parent.MerchantLightningD.Address.AbsoluteUri
|
connectionType == LightningConnectionType.CLightning ? parent.MerchantLightningD.Address.AbsoluteUri
|
||||||
: throw new NotSupportedException(connectionType.ToString()),
|
: throw new NotSupportedException(connectionType.ToString()),
|
||||||
SkipPortTest = true
|
SkipPortTest = true
|
||||||
|
|||||||
@@ -409,14 +409,14 @@ namespace BTCPayServer.Tests
|
|||||||
|
|
||||||
var testResult = storeController.AddLightningNode(user.StoreId, new LightningNodeViewModel()
|
var testResult = storeController.AddLightningNode(user.StoreId, new LightningNodeViewModel()
|
||||||
{
|
{
|
||||||
Url = tester.MerchantCharge.Client.Uri.AbsoluteUri
|
ConnectionString = tester.MerchantCharge.Client.Uri.AbsoluteUri
|
||||||
}, "test", "BTC").GetAwaiter().GetResult();
|
}, "test", "BTC").GetAwaiter().GetResult();
|
||||||
Assert.DoesNotContain("Error", ((LightningNodeViewModel)Assert.IsType<ViewResult>(testResult).Model).StatusMessage, StringComparison.OrdinalIgnoreCase);
|
Assert.DoesNotContain("Error", ((LightningNodeViewModel)Assert.IsType<ViewResult>(testResult).Model).StatusMessage, StringComparison.OrdinalIgnoreCase);
|
||||||
Assert.True(storeController.ModelState.IsValid);
|
Assert.True(storeController.ModelState.IsValid);
|
||||||
|
|
||||||
Assert.IsType<RedirectToActionResult>(storeController.AddLightningNode(user.StoreId, new LightningNodeViewModel()
|
Assert.IsType<RedirectToActionResult>(storeController.AddLightningNode(user.StoreId, new LightningNodeViewModel()
|
||||||
{
|
{
|
||||||
Url = tester.MerchantCharge.Client.Uri.AbsoluteUri
|
ConnectionString = tester.MerchantCharge.Client.Uri.AbsoluteUri
|
||||||
}, "save", "BTC").GetAwaiter().GetResult());
|
}, "save", "BTC").GetAwaiter().GetResult());
|
||||||
|
|
||||||
var storeVm = Assert.IsType<Models.StoreViewModels.StoreViewModel>(Assert.IsType<ViewResult>(storeController.UpdateStore()).Model);
|
var storeVm = Assert.IsType<Models.StoreViewModels.StoreViewModel>(Assert.IsType<ViewResult>(storeController.UpdateStore()).Model);
|
||||||
@@ -428,41 +428,84 @@ namespace BTCPayServer.Tests
|
|||||||
public void CanParseLightningURL()
|
public void CanParseLightningURL()
|
||||||
{
|
{
|
||||||
LightningConnectionString conn = null;
|
LightningConnectionString conn = null;
|
||||||
Assert.True(LightningConnectionString.TryParse("/test/a", out conn));
|
Assert.True(LightningConnectionString.TryParse("/test/a", true, out conn));
|
||||||
Assert.Equal("unix://test/a", conn.ToString());
|
for (int i = 0; i < 2; i++)
|
||||||
Assert.Equal("unix://test/a", conn.ToUri(true).AbsoluteUri);
|
{
|
||||||
Assert.Equal("unix://test/a", conn.ToUri(false).AbsoluteUri);
|
if (i == 1)
|
||||||
Assert.Equal(LightningConnectionType.CLightning, conn.ConnectionType);
|
Assert.True(LightningConnectionString.TryParse(conn.ToString(), false, out conn));
|
||||||
|
Assert.Equal(i == 0, conn.IsLegacy);
|
||||||
|
Assert.Equal("type=clightning;server=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.True(LightningConnectionString.TryParse("unix://test/a", true, out conn));
|
||||||
Assert.Equal("unix://test/a", conn.ToString());
|
for (int i = 0; i < 2; i++)
|
||||||
Assert.Equal("unix://test/a", conn.ToUri(true).AbsoluteUri);
|
{
|
||||||
Assert.Equal("unix://test/a", conn.ToUri(false).AbsoluteUri);
|
if (i == 1)
|
||||||
Assert.Equal(LightningConnectionType.CLightning, conn.ConnectionType);
|
Assert.True(LightningConnectionString.TryParse(conn.ToString(), false, out conn));
|
||||||
|
Assert.Equal("type=clightning;server=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.True(LightningConnectionString.TryParse("unix://test/a", true, out conn));
|
||||||
Assert.Equal("unix://test/a", conn.ToString());
|
for (int i = 0; i < 2; i++)
|
||||||
Assert.Equal("unix://test/a", conn.ToUri(true).AbsoluteUri);
|
{
|
||||||
Assert.Equal("unix://test/a", conn.ToUri(false).AbsoluteUri);
|
if (i == 1)
|
||||||
Assert.Equal(LightningConnectionType.CLightning, conn.ConnectionType);
|
Assert.True(LightningConnectionString.TryParse(conn.ToString(), false, out conn));
|
||||||
|
Assert.Equal("type=clightning;server=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.True(LightningConnectionString.TryParse("tcp://test/a", true, out conn));
|
||||||
Assert.Equal("tcp://test/a", conn.ToString());
|
for (int i = 0; i < 2; i++)
|
||||||
Assert.Equal("tcp://test/a", conn.ToUri(true).AbsoluteUri);
|
{
|
||||||
Assert.Equal("tcp://test/a", conn.ToUri(false).AbsoluteUri);
|
if (i == 1)
|
||||||
Assert.Equal(LightningConnectionType.CLightning, conn.ConnectionType);
|
Assert.True(LightningConnectionString.TryParse(conn.ToString(), false, out conn));
|
||||||
|
Assert.Equal("type=clightning;server=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.True(LightningConnectionString.TryParse("http://aaa:bbb@test/a", true, out conn));
|
||||||
Assert.Equal("http://aaa:bbb@test/a", conn.ToString());
|
for (int i = 0; i < 2; i++)
|
||||||
Assert.Equal("http://aaa:bbb@test/a", conn.ToUri(true).AbsoluteUri);
|
{
|
||||||
Assert.Equal("http://test/a", conn.ToUri(false).AbsoluteUri);
|
if (i == 1)
|
||||||
Assert.Equal(LightningConnectionType.Charge, conn.ConnectionType);
|
Assert.True(LightningConnectionString.TryParse(conn.ToString(), false, out conn));
|
||||||
Assert.Equal("aaa", conn.Username);
|
Assert.Equal("type=charge;server=http://aaa:bbb@test/a", conn.ToString());
|
||||||
Assert.Equal("bbb", conn.Password);
|
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.True(LightningConnectionString.TryParse("http://api-token:bbb@test/a", true, out conn));
|
||||||
Assert.False(LightningConnectionString.TryParse("https://test/a", out conn));
|
for (int i = 0; i < 2; i++)
|
||||||
Assert.False(LightningConnectionString.TryParse("unix://dwewoi:dwdwqd@test/a", out conn));
|
{
|
||||||
|
if (i == 1)
|
||||||
|
Assert.True(LightningConnectionString.TryParse(conn.ToString(), false, out conn));
|
||||||
|
Assert.Equal("type=charge;server=http://test/a;api-token=bbb", conn.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.False(LightningConnectionString.TryParse("lol://aaa:bbb@test/a", true, out conn));
|
||||||
|
Assert.False(LightningConnectionString.TryParse("https://test/a", true, out conn));
|
||||||
|
Assert.False(LightningConnectionString.TryParse("unix://dwewoi:dwdwqd@test/a", true, out conn));
|
||||||
|
Assert.False(LightningConnectionString.TryParse("tcp://test/a", false, out conn));
|
||||||
|
Assert.False(LightningConnectionString.TryParse("type=charge;server=http://aaa:bbb@test/a;unk=lol", false, out conn));
|
||||||
|
Assert.False(LightningConnectionString.TryParse("type=charge;server=tcp://aaa:bbb@test/a", false, out conn));
|
||||||
|
Assert.False(LightningConnectionString.TryParse("type=charge", false, out conn));
|
||||||
|
Assert.False(LightningConnectionString.TryParse("type=clightning", false, out conn));
|
||||||
|
Assert.True(LightningConnectionString.TryParse("type=clightning;server=tcp://aaa:bbb@test/a", false, out conn));
|
||||||
|
Assert.True(LightningConnectionString.TryParse("type=clightning;server=/aaa:bbb@test/a", false, out conn));
|
||||||
|
Assert.True(LightningConnectionString.TryParse("type=clightning;server=unix://aaa:bbb@test/a", false, out conn));
|
||||||
|
Assert.False(LightningConnectionString.TryParse("type=clightning;server=wtf://aaa:bbb@test/a", false, out conn));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|||||||
@@ -77,11 +77,15 @@ namespace BTCPayServer.Configuration
|
|||||||
var lightning = conf.GetOrDefault<string>($"{net.CryptoCode}.lightning", string.Empty);
|
var lightning = conf.GetOrDefault<string>($"{net.CryptoCode}.lightning", string.Empty);
|
||||||
if(lightning.Length != 0)
|
if(lightning.Length != 0)
|
||||||
{
|
{
|
||||||
if(!LightningConnectionString.TryParse(lightning, out var connectionString, out var error))
|
if(!LightningConnectionString.TryParse(lightning, true, out var connectionString, out var error))
|
||||||
{
|
{
|
||||||
throw new ConfigException($"Invalid setting {net.CryptoCode}.lightning, you need to pass either " +
|
throw new ConfigException($"Invalid setting {net.CryptoCode}.lightning, " + Environment.NewLine +
|
||||||
$"the absolute path to the unix socket of a running CLightning instance (eg. /root/.lightning/lightning-rpc), " +
|
$"If you have a lightning server use: 'type=clightning;server=/root/.lightning/lightning-rpc', " + Environment.NewLine +
|
||||||
$"or the url to a charge server with crendetials (eg. https://apitoken@API_TOKEN_SECRET:charge.example.com/)");
|
$"If you have a lightning charge server: 'type=charge;server=https://charge.example.com;api-token=yourapitoken'");
|
||||||
|
}
|
||||||
|
if(connectionString.IsLegacy)
|
||||||
|
{
|
||||||
|
Logs.Configuration.LogWarning($"Setting {net.CryptoCode}.lightning will work but use an deprecated format, please replace it by '{connectionString.ToString()}'");
|
||||||
}
|
}
|
||||||
InternalLightningByCryptoCode.Add(net.CryptoCode, connectionString);
|
InternalLightningByCryptoCode.Add(net.CryptoCode, connectionString);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,10 +115,11 @@ namespace BTCPayServer.Controllers
|
|||||||
entity.Status = "new";
|
entity.Status = "new";
|
||||||
entity.SpeedPolicy = ParseSpeedPolicy(invoice.TransactionSpeed, store.SpeedPolicy);
|
entity.SpeedPolicy = ParseSpeedPolicy(invoice.TransactionSpeed, store.SpeedPolicy);
|
||||||
|
|
||||||
|
|
||||||
HashSet<CurrencyPair> currencyPairsToFetch = new HashSet<CurrencyPair>();
|
HashSet<CurrencyPair> currencyPairsToFetch = new HashSet<CurrencyPair>();
|
||||||
var rules = storeBlob.GetRateRules(_NetworkProvider);
|
var rules = storeBlob.GetRateRules(_NetworkProvider);
|
||||||
|
|
||||||
|
await UpdateCLightningConnectionStringIfNeeded(store);
|
||||||
|
|
||||||
foreach (var network in store.GetSupportedPaymentMethods(_NetworkProvider)
|
foreach (var network in store.GetSupportedPaymentMethods(_NetworkProvider)
|
||||||
.Select(c => _NetworkProvider.GetNetwork(c.PaymentId.CryptoCode))
|
.Select(c => _NetworkProvider.GetNetwork(c.PaymentId.CryptoCode))
|
||||||
.Where(c => c != null))
|
.Where(c => c != null))
|
||||||
@@ -208,6 +209,22 @@ namespace BTCPayServer.Controllers
|
|||||||
return new DataWrapper<InvoiceResponse>(resp) { Facade = "pos/invoice" };
|
return new DataWrapper<InvoiceResponse>(resp) { Facade = "pos/invoice" };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task UpdateCLightningConnectionStringIfNeeded(StoreData store)
|
||||||
|
{
|
||||||
|
bool needUpdate = false;
|
||||||
|
foreach (var method in store.GetSupportedPaymentMethods(_NetworkProvider).OfType<Payments.Lightning.LightningSupportedPaymentMethod>())
|
||||||
|
{
|
||||||
|
var lightning = method.GetLightningUrl();
|
||||||
|
if (lightning.IsLegacy)
|
||||||
|
{
|
||||||
|
method.SetLightningUrl(lightning);
|
||||||
|
needUpdate = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(needUpdate)
|
||||||
|
await _StoreRepository.UpdateStore(store);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<PaymentMethod> CreatePaymentMethodAsync(Dictionary<CurrencyPair, Task<RateResult>> fetchingByCurrencyPair, IPaymentMethodHandler handler, ISupportedPaymentMethod supportedPaymentMethod, BTCPayNetwork network, InvoiceEntity entity, StoreData store)
|
private async Task<PaymentMethod> CreatePaymentMethodAsync(Dictionary<CurrencyPair, Task<RateResult>> fetchingByCurrencyPair, IPaymentMethodHandler handler, ISupportedPaymentMethod supportedPaymentMethod, BTCPayNetwork network, InvoiceEntity entity, StoreData store)
|
||||||
{
|
{
|
||||||
var storeBlob = store.GetStoreBlob();
|
var storeBlob = store.GetStoreBlob();
|
||||||
|
|||||||
@@ -26,14 +26,14 @@ namespace BTCPayServer.Controllers
|
|||||||
return NotFound();
|
return NotFound();
|
||||||
LightningNodeViewModel vm = new LightningNodeViewModel();
|
LightningNodeViewModel vm = new LightningNodeViewModel();
|
||||||
vm.CryptoCode = cryptoCode;
|
vm.CryptoCode = cryptoCode;
|
||||||
vm.InternalLightningNode = GetInternalLighningNode(cryptoCode)?.ToUri(true)?.AbsoluteUri;
|
vm.InternalLightningNode = GetInternalLighningNode(cryptoCode)?.ToString();
|
||||||
SetExistingValues(store, vm);
|
SetExistingValues(store, vm);
|
||||||
return View(vm);
|
return View(vm);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetExistingValues(StoreData store, LightningNodeViewModel vm)
|
private void SetExistingValues(StoreData store, LightningNodeViewModel vm)
|
||||||
{
|
{
|
||||||
vm.Url = GetExistingLightningSupportedPaymentMethod(vm.CryptoCode, store)?.GetLightningUrl()?.ToString();
|
vm.ConnectionString = GetExistingLightningSupportedPaymentMethod(vm.CryptoCode, store)?.GetLightningUrl()?.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private LightningSupportedPaymentMethod GetExistingLightningSupportedPaymentMethod(string cryptoCode, StoreData store)
|
private LightningSupportedPaymentMethod GetExistingLightningSupportedPaymentMethod(string cryptoCode, StoreData store)
|
||||||
@@ -65,7 +65,7 @@ namespace BTCPayServer.Controllers
|
|||||||
var network = vm.CryptoCode == null ? null : _ExplorerProvider.GetNetwork(vm.CryptoCode);
|
var network = vm.CryptoCode == null ? null : _ExplorerProvider.GetNetwork(vm.CryptoCode);
|
||||||
|
|
||||||
var internalLightning = GetInternalLighningNode(network.CryptoCode);
|
var internalLightning = GetInternalLighningNode(network.CryptoCode);
|
||||||
vm.InternalLightningNode = internalLightning?.ToUri(true)?.AbsoluteUri;
|
vm.InternalLightningNode = internalLightning?.ToString();
|
||||||
if (network == null)
|
if (network == null)
|
||||||
{
|
{
|
||||||
ModelState.AddModelError(nameof(vm.CryptoCode), "Invalid network");
|
ModelState.AddModelError(nameof(vm.CryptoCode), "Invalid network");
|
||||||
@@ -74,11 +74,11 @@ namespace BTCPayServer.Controllers
|
|||||||
|
|
||||||
PaymentMethodId paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.LightningLike);
|
PaymentMethodId paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.LightningLike);
|
||||||
Payments.Lightning.LightningSupportedPaymentMethod paymentMethod = null;
|
Payments.Lightning.LightningSupportedPaymentMethod paymentMethod = null;
|
||||||
if (!string.IsNullOrEmpty(vm.Url))
|
if (!string.IsNullOrEmpty(vm.ConnectionString))
|
||||||
{
|
{
|
||||||
if (!LightningConnectionString.TryParse(vm.Url, out var connectionString, out var error))
|
if (!LightningConnectionString.TryParse(vm.ConnectionString, false, out var connectionString, out var error))
|
||||||
{
|
{
|
||||||
ModelState.AddModelError(nameof(vm.Url), $"Invalid URL ({error})");
|
ModelState.AddModelError(nameof(vm.ConnectionString), $"Invalid URL ({error})");
|
||||||
return View(vm);
|
return View(vm);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,14 +93,14 @@ namespace BTCPayServer.Controllers
|
|||||||
{
|
{
|
||||||
if (!isInternalNode || (isInternalNode && !CanUseInternalLightning()))
|
if (!isInternalNode || (isInternalNode && !CanUseInternalLightning()))
|
||||||
{
|
{
|
||||||
ModelState.AddModelError(nameof(vm.Url), "The url must be HTTPS");
|
ModelState.AddModelError(nameof(vm.ConnectionString), "The url must be HTTPS");
|
||||||
return View(vm);
|
return View(vm);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isInternalNode && !CanUseInternalLightning())
|
if (isInternalNode && !CanUseInternalLightning())
|
||||||
{
|
{
|
||||||
ModelState.AddModelError(nameof(vm.Url), "Unauthorized url");
|
ModelState.AddModelError(nameof(vm.ConnectionString), "Unauthorized url");
|
||||||
return View(vm);
|
return View(vm);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,7 +121,7 @@ namespace BTCPayServer.Controllers
|
|||||||
{
|
{
|
||||||
if (paymentMethod == null)
|
if (paymentMethod == null)
|
||||||
{
|
{
|
||||||
ModelState.AddModelError(nameof(vm.Url), "Missing url parameter");
|
ModelState.AddModelError(nameof(vm.ConnectionString), "Missing url parameter");
|
||||||
return View(vm);
|
return View(vm);
|
||||||
}
|
}
|
||||||
var handler = (LightningLikePaymentHandler)_ServiceProvider.GetRequiredService<IPaymentMethodHandler<Payments.Lightning.LightningSupportedPaymentMethod>>();
|
var handler = (LightningLikePaymentHandler)_ServiceProvider.GetRequiredService<IPaymentMethodHandler<Payments.Lightning.LightningSupportedPaymentMethod>>();
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ namespace BTCPayServer.Models.StoreViewModels
|
|||||||
{
|
{
|
||||||
public class LightningNodeViewModel
|
public class LightningNodeViewModel
|
||||||
{
|
{
|
||||||
[Display(Name = "Lightning charge url")]
|
[Display(Name = "Connection string")]
|
||||||
public string Url
|
public string ConnectionString
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace BTCPayServer.Payments.Lightning
|
namespace BTCPayServer.Payments.Lightning
|
||||||
@@ -12,14 +13,168 @@ namespace BTCPayServer.Payments.Lightning
|
|||||||
}
|
}
|
||||||
public class LightningConnectionString
|
public class LightningConnectionString
|
||||||
{
|
{
|
||||||
public static bool TryParse(string str, out LightningConnectionString connectionString)
|
static Dictionary<string, LightningConnectionType> typeMapping;
|
||||||
|
static Dictionary<LightningConnectionType, string> typeMappingReverse;
|
||||||
|
static LightningConnectionString()
|
||||||
{
|
{
|
||||||
return TryParse(str, out connectionString, out var error);
|
typeMapping = new Dictionary<string, LightningConnectionType>();
|
||||||
|
typeMapping.Add("clightning", LightningConnectionType.CLightning);
|
||||||
|
typeMapping.Add("charge", LightningConnectionType.Charge);
|
||||||
|
typeMappingReverse = new Dictionary<LightningConnectionType, string>();
|
||||||
|
foreach (var kv in typeMapping)
|
||||||
|
{
|
||||||
|
typeMappingReverse.Add(kv.Value, kv.Key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
public static bool TryParse(string str, out LightningConnectionString connectionString, out string error)
|
public static bool TryParse(string str, bool supportLegacy, out LightningConnectionString connectionString)
|
||||||
|
{
|
||||||
|
return TryParse(str, supportLegacy, out connectionString, out var error);
|
||||||
|
}
|
||||||
|
public static bool TryParse(string str, bool supportLegacy, out LightningConnectionString connectionString, out string error)
|
||||||
{
|
{
|
||||||
if (str == null)
|
if (str == null)
|
||||||
throw new ArgumentNullException(nameof(str));
|
throw new ArgumentNullException(nameof(str));
|
||||||
|
|
||||||
|
if (supportLegacy)
|
||||||
|
{
|
||||||
|
var parsed = TryParseLegacy(str, out connectionString, out error);
|
||||||
|
if (!parsed)
|
||||||
|
{
|
||||||
|
parsed = TryParseNewFormat(str, out connectionString, out error);
|
||||||
|
}
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return TryParseNewFormat(str, out connectionString, out error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryParseNewFormat(string str, out LightningConnectionString connectionString, out string error)
|
||||||
|
{
|
||||||
|
connectionString = null;
|
||||||
|
error = null;
|
||||||
|
var parts = str.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
Dictionary<string, string> keyValues = new Dictionary<string, string>();
|
||||||
|
foreach (var part in parts.Select(p => p.Trim()))
|
||||||
|
{
|
||||||
|
var idx = part.IndexOf('=', StringComparison.OrdinalIgnoreCase);
|
||||||
|
if (idx == -1)
|
||||||
|
{
|
||||||
|
error = "The format of the connectionString should a list of key=value delimited by semicolon";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var key = part.Substring(0, idx).Trim().ToLowerInvariant();
|
||||||
|
var value = part.Substring(idx + 1).Trim();
|
||||||
|
if (keyValues.ContainsKey(key))
|
||||||
|
{
|
||||||
|
error = $"Duplicate key {key}";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
keyValues.Add(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
var possibleTypes = String.Join(", ", typeMapping.Select(k => k.Key).ToArray());
|
||||||
|
|
||||||
|
LightningConnectionString result = new LightningConnectionString();
|
||||||
|
var type = Take(keyValues, "type");
|
||||||
|
if (type == null)
|
||||||
|
{
|
||||||
|
error = $"The key 'type' is mandatory, possible values are {possibleTypes}";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!typeMapping.TryGetValue(type.ToLowerInvariant(), out var connectionType))
|
||||||
|
{
|
||||||
|
error = $"The key 'type' is invalid, possible values are {possibleTypes}";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.ConnectionType = connectionType;
|
||||||
|
|
||||||
|
switch (connectionType)
|
||||||
|
{
|
||||||
|
case LightningConnectionType.Charge:
|
||||||
|
{
|
||||||
|
var server = Take(keyValues, "server");
|
||||||
|
if (server == null)
|
||||||
|
{
|
||||||
|
error = $"The key 'server' is mandatory for charge connection strings";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Uri.TryCreate(server, UriKind.Absolute, out var uri)
|
||||||
|
|| (uri.Scheme != "http" && uri.Scheme != "https"))
|
||||||
|
{
|
||||||
|
error = $"The key 'server' should be an URI starting by http:// or https://";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
parts = uri.UserInfo.Split(':');
|
||||||
|
if (!string.IsNullOrEmpty(uri.UserInfo) && parts.Length == 2)
|
||||||
|
{
|
||||||
|
result.Username = parts[0];
|
||||||
|
result.Password = parts[1];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var apiToken = Take(keyValues, "api-token");
|
||||||
|
if (apiToken == null)
|
||||||
|
{
|
||||||
|
error = "The key 'api-token' is not found";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
result.Username = "api-token";
|
||||||
|
result.Password = apiToken;
|
||||||
|
}
|
||||||
|
result.BaseUri = new UriBuilder(uri) { UserName = "", Password = "" }.Uri;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case LightningConnectionType.CLightning:
|
||||||
|
{
|
||||||
|
var server = Take(keyValues, "server");
|
||||||
|
if (server == null)
|
||||||
|
{
|
||||||
|
error = $"The key 'server' is mandatory for charge connection strings";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (server.StartsWith("//", StringComparison.OrdinalIgnoreCase))
|
||||||
|
server = "unix:" + str;
|
||||||
|
else if (server.StartsWith("/", StringComparison.OrdinalIgnoreCase))
|
||||||
|
server = "unix:/" + str;
|
||||||
|
|
||||||
|
if (!Uri.TryCreate(server, UriKind.Absolute, out var uri)
|
||||||
|
|| (uri.Scheme != "tcp" && uri.Scheme != "unix"))
|
||||||
|
{
|
||||||
|
error = $"The key 'server' should be an URI starting by tcp:// or unix:// or a path to the 'lightning-rpc' unix socket";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
result.BaseUri = uri;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new NotSupportedException(connectionType.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyValues.Count != 0)
|
||||||
|
{
|
||||||
|
error = $"Unknown keys ({String.Join(", ", keyValues.Select(k => k.Key).ToArray())})";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectionString = result;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
private static string Take(Dictionary<string, string> keyValues, string key)
|
||||||
|
{
|
||||||
|
if (keyValues.TryGetValue(key, out var v))
|
||||||
|
keyValues.Remove(key);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryParseLegacy(string str, out LightningConnectionString connectionString, out string error)
|
||||||
|
{
|
||||||
if (str.StartsWith('/'))
|
if (str.StartsWith('/'))
|
||||||
str = "unix:" + str;
|
str = "unix:" + str;
|
||||||
var result = new LightningConnectionString();
|
var result = new LightningConnectionString();
|
||||||
@@ -49,8 +204,12 @@ namespace BTCPayServer.Payments.Lightning
|
|||||||
str = str.Substring(1);
|
str = str.Substring(1);
|
||||||
}
|
}
|
||||||
uri = new Uri("unix://" + str, UriKind.Absolute);
|
uri = new Uri("unix://" + str, UriKind.Absolute);
|
||||||
|
result.ConnectionType = LightningConnectionType.CLightning;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (uri.Scheme == "tcp")
|
||||||
|
result.ConnectionType = LightningConnectionType.CLightning;
|
||||||
|
|
||||||
if (uri.Scheme == "http" || uri.Scheme == "https")
|
if (uri.Scheme == "http" || uri.Scheme == "https")
|
||||||
{
|
{
|
||||||
var parts = uri.UserInfo.Split(':');
|
var parts = uri.UserInfo.Split(':');
|
||||||
@@ -61,6 +220,7 @@ namespace BTCPayServer.Payments.Lightning
|
|||||||
}
|
}
|
||||||
result.Username = parts[0];
|
result.Username = parts[0];
|
||||||
result.Password = parts[1];
|
result.Password = parts[1];
|
||||||
|
result.ConnectionType = LightningConnectionType.Charge;
|
||||||
}
|
}
|
||||||
else if (!string.IsNullOrEmpty(uri.UserInfo))
|
else if (!string.IsNullOrEmpty(uri.UserInfo))
|
||||||
{
|
{
|
||||||
@@ -68,6 +228,7 @@ namespace BTCPayServer.Payments.Lightning
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
result.BaseUri = new UriBuilder(uri) { UserName = "", Password = "" }.Uri;
|
result.BaseUri = new UriBuilder(uri) { UserName = "", Password = "" }.Uri;
|
||||||
|
result.IsLegacy = true;
|
||||||
connectionString = result;
|
connectionString = result;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -80,14 +241,12 @@ namespace BTCPayServer.Payments.Lightning
|
|||||||
public string Username { get; set; }
|
public string Username { get; set; }
|
||||||
public string Password { get; set; }
|
public string Password { get; set; }
|
||||||
public Uri BaseUri { get; set; }
|
public Uri BaseUri { get; set; }
|
||||||
|
public bool IsLegacy { get; private set; }
|
||||||
|
|
||||||
public LightningConnectionType ConnectionType
|
public LightningConnectionType ConnectionType
|
||||||
{
|
{
|
||||||
get
|
get;
|
||||||
{
|
private set;
|
||||||
return BaseUri.Scheme == "http" || BaseUri.Scheme == "https" ? LightningConnectionType.Charge
|
|
||||||
: LightningConnectionType.CLightning;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Uri ToUri(bool withCredentials)
|
public Uri ToUri(bool withCredentials)
|
||||||
@@ -104,7 +263,28 @@ namespace BTCPayServer.Payments.Lightning
|
|||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return ToUri(true).AbsoluteUri;
|
var type = typeMappingReverse[ConnectionType];
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
builder.Append($"type={type}");
|
||||||
|
switch (ConnectionType)
|
||||||
|
{
|
||||||
|
case LightningConnectionType.Charge:
|
||||||
|
if(Username == null || Username == "api-token")
|
||||||
|
{
|
||||||
|
builder.Append($";server={BaseUri};api-token={Password}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
builder.Append($";server={ToUri(true)}");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case LightningConnectionType.CLightning:
|
||||||
|
builder.Append($";server={BaseUri}");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new NotSupportedException(type);
|
||||||
|
}
|
||||||
|
return builder.ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,15 +11,29 @@ namespace BTCPayServer.Payments.Lightning
|
|||||||
[Obsolete("Use Get/SetLightningUrl")]
|
[Obsolete("Use Get/SetLightningUrl")]
|
||||||
public string LightningChargeUrl { get; set; }
|
public string LightningChargeUrl { get; set; }
|
||||||
|
|
||||||
|
[Obsolete("Use Get/SetLightningUrl")]
|
||||||
|
public string LightningConnectionString { get; set; }
|
||||||
|
|
||||||
public LightningConnectionString GetLightningUrl()
|
public LightningConnectionString GetLightningUrl()
|
||||||
{
|
{
|
||||||
#pragma warning disable CS0618 // Type or member is obsolete
|
#pragma warning disable CS0618 // Type or member is obsolete
|
||||||
var fullUri = new UriBuilder(LightningChargeUrl) { UserName = Username, Password = Password }.Uri.AbsoluteUri;
|
if (!string.IsNullOrEmpty(LightningConnectionString))
|
||||||
if(!LightningConnectionString.TryParse(fullUri, out var connectionString, out var error))
|
|
||||||
{
|
{
|
||||||
throw new FormatException(error);
|
if (!BTCPayServer.Payments.Lightning.LightningConnectionString.TryParse(LightningConnectionString, false, out var connectionString, out var error))
|
||||||
|
{
|
||||||
|
throw new FormatException(error);
|
||||||
|
}
|
||||||
|
return connectionString;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var fullUri = new UriBuilder(LightningChargeUrl) { UserName = Username, Password = Password }.Uri.AbsoluteUri;
|
||||||
|
if (!BTCPayServer.Payments.Lightning.LightningConnectionString.TryParse(fullUri, true, out var connectionString, out var error))
|
||||||
|
{
|
||||||
|
throw new FormatException(error);
|
||||||
|
}
|
||||||
|
return connectionString;
|
||||||
}
|
}
|
||||||
return connectionString;
|
|
||||||
#pragma warning restore CS0618 // Type or member is obsolete
|
#pragma warning restore CS0618 // Type or member is obsolete
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,9 +43,10 @@ namespace BTCPayServer.Payments.Lightning
|
|||||||
throw new ArgumentNullException(nameof(connectionString));
|
throw new ArgumentNullException(nameof(connectionString));
|
||||||
|
|
||||||
#pragma warning disable CS0618 // Type or member is obsolete
|
#pragma warning disable CS0618 // Type or member is obsolete
|
||||||
Username = connectionString.Username;
|
LightningConnectionString = connectionString.ToString();
|
||||||
Password = connectionString.Password;
|
Username = null;
|
||||||
LightningChargeUrl = connectionString.BaseUri.AbsoluteUri;
|
Password = null;
|
||||||
|
LightningChargeUrl = null;
|
||||||
#pragma warning restore CS0618 // Type or member is obsolete
|
#pragma warning restore CS0618 // Type or member is obsolete
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,9 +36,9 @@
|
|||||||
<span>This URL should point to an installed lightning charge server for @Model.CryptoCode</span>
|
<span>This URL should point to an installed lightning charge server for @Model.CryptoCode</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label asp-for="Url"></label>
|
<label asp-for="ConnectionString"></label>
|
||||||
<input id="lightningurl" asp-for="Url" class="form-control" />
|
<input id="lightningurl" asp-for="ConnectionString" class="form-control" />
|
||||||
<span asp-validation-for="Url" class="text-danger"></span>
|
<span asp-validation-for="ConnectionString" class="text-danger"></span>
|
||||||
@if(Model.InternalLightningNode != null)
|
@if(Model.InternalLightningNode != null)
|
||||||
{
|
{
|
||||||
<p class="form-text text-muted">
|
<p class="form-text text-muted">
|
||||||
|
|||||||
Reference in New Issue
Block a user