From 7e714f1ef8b4155f4060f6c705afe47073916623 Mon Sep 17 00:00:00 2001 From: Nicolas Dorier Date: Tue, 2 Mar 2021 11:11:58 +0900 Subject: [PATCH] Refactor how we handle and validate LN ConnectionStrings (#2314) * Refactor how we handle and validate LN ConnectionStrings * Migrate existing connection string to Internal Node if they are the same. Cleanup some obsolete fields * Fix typos, remove duplicated method * Add a InternalNodeRef to LightningSupportedPaymentMethod --- .../AltcoinTests/AltcoinTests.cs | 6 +- BTCPayServer.Tests/BTCPayServerTester.cs | 4 +- BTCPayServer.Tests/CheckoutUITests.cs | 2 +- BTCPayServer.Tests/GreenfieldAPITests.cs | 81 +++++++++-- BTCPayServer.Tests/ServerTester.cs | 40 +++++- BTCPayServer.Tests/TestAccount.cs | 36 +---- BTCPayServer.Tests/UnitTest1.cs | 126 +++++++++++++++--- .../LightningNodeApiController.Internal.cs | 19 +-- .../LightningNodeApiController.Store.cs | 27 ++-- .../GreenField/LightningNodeApiController.cs | 15 ++- ...ightningNetworkPaymentMethodsController.cs | 93 +++++-------- .../StoresController.LightningLike.cs | 72 +++------- BTCPayServer/Controllers/StoresController.cs | 4 +- BTCPayServer/Data/StoreDataExtensions.cs | 20 +-- BTCPayServer/Extensions.cs | 19 ++- BTCPayServer/Hosting/MigrationStartupTask.cs | 113 +++++++++++++++- .../StoreViewModels/LightningNodeViewModel.cs | 3 +- .../Lightning/LightningLikePaymentHandler.cs | 28 +++- .../Payments/Lightning/LightningListener.cs | 57 +++++--- .../LightningSupportedPaymentMethod.cs | 63 ++++++--- .../Services/BTCPayServerEnvironment.cs | 4 +- BTCPayServer/Services/MigrationSettings.cs | 1 + .../Views/Stores/AddLightningNode.cshtml | 34 ++--- ...res-payment-methods.lightning-network.json | 2 +- 24 files changed, 572 insertions(+), 297 deletions(-) diff --git a/BTCPayServer.Tests/AltcoinTests/AltcoinTests.cs b/BTCPayServer.Tests/AltcoinTests/AltcoinTests.cs index e28c7d436..762411bf5 100644 --- a/BTCPayServer.Tests/AltcoinTests/AltcoinTests.cs +++ b/BTCPayServer.Tests/AltcoinTests/AltcoinTests.cs @@ -80,7 +80,7 @@ namespace BTCPayServer.Tests tester.ActivateLightning(); await tester.StartAsync(); var user = tester.NewAccount(); - user.GrantAccess(); + user.GrantAccess(true); user.RegisterDerivationScheme("BTC"); user.RegisterDerivationScheme("LTC"); user.RegisterLightningNode("BTC", LightningConnectionType.CLightning); @@ -287,7 +287,7 @@ namespace BTCPayServer.Tests await tester.StartAsync(); await tester.EnsureChannelsSetup(); var user = tester.NewAccount(); - user.GrantAccess(); + user.GrantAccess(true); user.RegisterLightningNode("BTC", LightningConnectionType.Charge); user.RegisterDerivationScheme("BTC"); user.RegisterDerivationScheme("LTC"); @@ -876,7 +876,7 @@ normal: var paymentMethodHandlerDictionary = new PaymentMethodHandlerDictionary(new IPaymentMethodHandler[] { new BitcoinLikePaymentHandler(null, networkProvider, null, null, null), - new LightningLikePaymentHandler(null, null, networkProvider, null, null), + new LightningLikePaymentHandler(null, null, networkProvider, null, null, null), }); var networkBTC = networkProvider.GetNetwork("BTC"); var networkLTC = networkProvider.GetNetwork("LTC"); diff --git a/BTCPayServer.Tests/BTCPayServerTester.cs b/BTCPayServer.Tests/BTCPayServerTester.cs index 580777678..be57a77bf 100644 --- a/BTCPayServer.Tests/BTCPayServerTester.cs +++ b/BTCPayServer.Tests/BTCPayServerTester.cs @@ -112,7 +112,7 @@ namespace BTCPayServer.Tests if (UseLightning) { - config.AppendLine($"btc.lightning={IntegratedLightning.AbsoluteUri}"); + config.AppendLine($"btc.lightning={IntegratedLightning}"); var localLndBackupFile = Path.Combine(_Directory, "walletunlock.json"); File.Copy(TestUtils.GetTestDataFullPath("LndSeedBackup/walletunlock.json"), localLndBackupFile, true); config.AppendLine($"btc.external.lndseedbackup={localLndBackupFile}"); @@ -269,7 +269,7 @@ namespace BTCPayServer.Tests public InvoiceRepository InvoiceRepository { get; private set; } public StoreRepository StoreRepository { get; private set; } public BTCPayNetworkProvider Networks { get; private set; } - public Uri IntegratedLightning { get; internal set; } + public string IntegratedLightning { get; internal set; } public bool InContainer { get; internal set; } public T GetService() diff --git a/BTCPayServer.Tests/CheckoutUITests.cs b/BTCPayServer.Tests/CheckoutUITests.cs index 575d53c59..13ad0850a 100644 --- a/BTCPayServer.Tests/CheckoutUITests.cs +++ b/BTCPayServer.Tests/CheckoutUITests.cs @@ -109,7 +109,7 @@ namespace BTCPayServer.Tests s.Server.ActivateLightning(); await s.StartAsync(); s.GoToRegister(); - s.RegisterNewUser(); + s.RegisterNewUser(true); var store = s.CreateNewStore(); s.AddInternalLightningNode("BTC"); s.GoToStore(store.storeId, StoreNavPages.Checkout); diff --git a/BTCPayServer.Tests/GreenfieldAPITests.cs b/BTCPayServer.Tests/GreenfieldAPITests.cs index d8171c98d..b3b77d412 100644 --- a/BTCPayServer.Tests/GreenfieldAPITests.cs +++ b/BTCPayServer.Tests/GreenfieldAPITests.cs @@ -540,7 +540,7 @@ namespace BTCPayServer.Tests } } - private async Task AssertValidationError(string[] fields, Func act) + private async Task AssertValidationError(string[] fields, Func act) { var remainingFields = fields.ToHashSet(); var ex = await Assert.ThrowsAsync(act); @@ -550,6 +550,7 @@ namespace BTCPayServer.Tests remainingFields.Remove(field); } Assert.Empty(remainingFields); + return ex; } private async Task AssertHttpError(int code, Func act) @@ -1112,7 +1113,6 @@ namespace BTCPayServer.Tests merchant.RegisterLightningNode("BTC", LightningConnectionType.LndREST); var merchantClient = await merchant.CreateClient($"{Policies.CanUseLightningNodeInStore}:{merchant.StoreId}"); var merchantInvoice = await merchantClient.CreateLightningInvoice(merchant.StoreId, "BTC", new CreateLightningInvoiceRequest(LightMoney.Satoshis(1_000), "hey", TimeSpan.FromSeconds(60))); - tester.PayTester.GetService().DevelopmentOverride = false; // The default client is using charge, so we should not be able to query channels var client = await user.CreateClient(Policies.CanUseInternalLightningNode); @@ -1291,33 +1291,86 @@ namespace BTCPayServer.Tests tester.ActivateLightning(); await tester.StartAsync(); await tester.EnsureChannelsSetup(); - var user = tester.NewAccount(); - await user.GrantAccessAsync(true); - var client = await user.CreateClient(Policies.CanModifyStoreSettings); - var viewOnlyClient = await user.CreateClient(Policies.CanViewStoreSettings); - tester.PayTester.GetService().DevelopmentOverride = false; - var store = await client.GetStore(user.StoreId); + var admin = tester.NewAccount(); + await admin.GrantAccessAsync(true); + var admin2 = tester.NewAccount(); + await admin2.GrantAccessAsync(true); + var adminClient = await admin.CreateClient(Policies.CanModifyStoreSettings); + var admin2Client = await admin2.CreateClient(Policies.CanModifyStoreSettings, Policies.CanModifyServerSettings); + var viewOnlyClient = await admin.CreateClient(Policies.CanViewStoreSettings); + var store = await adminClient.GetStore(admin.StoreId); - Assert.Empty(await client.GetStoreLightningNetworkPaymentMethods(store.Id)); + Assert.Empty(await adminClient.GetStoreLightningNetworkPaymentMethods(store.Id)); await AssertHttpError(403, async () => { await viewOnlyClient.UpdateStoreLightningNetworkPaymentMethod(store.Id, "BTC", new LightningNetworkPaymentMethodData() { }); }); await AssertHttpError(404, async () => { - await client.GetStoreLightningNetworkPaymentMethod(store.Id, "BTC"); + await adminClient.GetStoreLightningNetworkPaymentMethod(store.Id, "BTC"); }); - await user.RegisterLightningNodeAsync("BTC", LightningConnectionType.CLightning, false); + await admin.RegisterLightningNodeAsync("BTC", false); - var method = await client.GetStoreLightningNetworkPaymentMethod(store.Id, "BTC"); + var method = await adminClient.GetStoreLightningNetworkPaymentMethod(store.Id, "BTC"); await AssertHttpError(403, async () => { await viewOnlyClient.RemoveStoreOnChainPaymentMethod(store.Id, "BTC"); }); - await client.RemoveStoreOnChainPaymentMethod(store.Id, "BTC"); + await adminClient.RemoveStoreOnChainPaymentMethod(store.Id, "BTC"); await AssertHttpError(404, async () => { - await client.GetStoreOnChainPaymentMethod(store.Id, "BTC"); + await adminClient.GetStoreOnChainPaymentMethod(store.Id, "BTC"); + }); + + + // Let's verify that the admin client can't change LN to unsafe connection strings without modify server settings rights + foreach (var forbidden in new string[] + { + "type=clightning;server=tcp://127.0.0.1", + "type=clightning;server=tcp://test", + "type=clightning;server=tcp://test.lan", + "type=clightning;server=tcp://test.local", + "type=clightning;server=tcp://192.168.1.2", + "type=clightning;server=unix://8.8.8.8", + "type=clightning;server=unix://[::1]", + "type=clightning;server=unix://[0:0:0:0:0:0:0:1]", + }) + { + var ex = await AssertValidationError(new[] { "ConnectionString" }, async () => + { + await adminClient.UpdateStoreLightningNetworkPaymentMethod(store.Id, "BTC", new LightningNetworkPaymentMethodData() + { + ConnectionString = forbidden, + CryptoCode = "BTC", + Enabled = true + }); + }); + Assert.Contains("btcpay.server.canmodifyserversettings", ex.Message); + // However, the other client should work because he has `btcpay.server.canmodifyserversettings` + await admin2Client.UpdateStoreLightningNetworkPaymentMethod(admin2.StoreId, "BTC", new LightningNetworkPaymentMethodData() + { + ConnectionString = forbidden, + CryptoCode = "BTC", + Enabled = true + }); + } + // Allowed ip should be ok + await adminClient.UpdateStoreLightningNetworkPaymentMethod(store.Id, "BTC", new LightningNetworkPaymentMethodData() + { + ConnectionString = "type=clightning;server=tcp://8.8.8.8", + CryptoCode = "BTC", + Enabled = true + }); + // If we strip the admin's right, he should not be able to set unsafe anymore, even if the API key is still valid + await admin2.MakeAdmin(false); + await AssertValidationError(new[] { "ConnectionString" }, async () => + { + await admin2Client.UpdateStoreLightningNetworkPaymentMethod(admin2.StoreId, "BTC", new LightningNetworkPaymentMethodData() + { + ConnectionString = "type=clightning;server=tcp://127.0.0.1", + CryptoCode = "BTC", + Enabled = true + }); }); var settings = (await tester.PayTester.GetService().GetSettingAsync())?? new PoliciesSettings(); diff --git a/BTCPayServer.Tests/ServerTester.cs b/BTCPayServer.Tests/ServerTester.cs index c1826daa0..efeaad981 100644 --- a/BTCPayServer.Tests/ServerTester.cs +++ b/BTCPayServer.Tests/ServerTester.cs @@ -7,6 +7,8 @@ using System.Net.Http; using System.Runtime.CompilerServices; using System.Threading.Tasks; using BTCPayServer.Lightning; +using BTCPayServer.Lightning.CLightning; +using BTCPayServer.Payments.Lightning; using BTCPayServer.Tests.Lnd; using BTCPayServer.Tests.Logging; using NBitcoin; @@ -86,6 +88,10 @@ namespace BTCPayServer.Tests #endif public void ActivateLightning() + { + ActivateLightning(LightningConnectionType.Charge); + } + public void ActivateLightning(LightningConnectionType internalNode) { var btc = NetworkProvider.GetNetwork("BTC").NBitcoinNetwork; CustomerLightningD = LightningClientFactory.CreateClient(GetEnvironment("TEST_CUSTOMERLIGHTNINGD", "type=clightning;server=tcp://127.0.0.1:30992/"), btc); @@ -93,7 +99,39 @@ namespace BTCPayServer.Tests MerchantCharge = new ChargeTester(this, "TEST_MERCHANTCHARGE", "type=charge;server=http://127.0.0.1:54938/;api-token=foiewnccewuify;allowinsecure=true", "merchant_lightningd", btc); MerchantLnd = new LndMockTester(this, "TEST_MERCHANTLND", "http://lnd:lnd@127.0.0.1:35531/", "merchant_lnd", btc); PayTester.UseLightning = true; - PayTester.IntegratedLightning = MerchantCharge.Client.Uri; + PayTester.IntegratedLightning = GetLightningConnectionString(internalNode, true); + } + public string GetLightningConnectionString(LightningConnectionType? connectionType, bool isMerchant) + { + string connectionString = null; + if (connectionType is null) + return LightningSupportedPaymentMethod.InternalNode; + if (connectionType == LightningConnectionType.Charge) + { + if (isMerchant) + connectionString = $"type=charge;server={MerchantCharge.Client.Uri.AbsoluteUri};allowinsecure=true"; + else + throw new NotSupportedException(); + } + else if (connectionType == LightningConnectionType.CLightning) + { + if (isMerchant) + connectionString = "type=clightning;server=" + + ((CLightningClient)MerchantLightningD).Address.AbsoluteUri; + else + connectionString = "type=clightning;server=" + + ((CLightningClient)CustomerLightningD).Address.AbsoluteUri; + } + else if (connectionType == LightningConnectionType.LndREST) + { + if (isMerchant) + connectionString = $"type=lnd-rest;server={MerchantLnd.Swagger.BaseUrl};allowinsecure=true"; + else + throw new NotSupportedException(); + } + else + throw new NotSupportedException(connectionType.ToString()); + return connectionString; } public bool Dockerized diff --git a/BTCPayServer.Tests/TestAccount.cs b/BTCPayServer.Tests/TestAccount.cs index bc3720f71..93e18b469 100644 --- a/BTCPayServer.Tests/TestAccount.cs +++ b/BTCPayServer.Tests/TestAccount.cs @@ -258,40 +258,18 @@ namespace BTCPayServer.Tests { RegisterLightningNodeAsync(cryptoCode, connectionType, isMerchant).GetAwaiter().GetResult(); } - - public async Task RegisterLightningNodeAsync(string cryptoCode, LightningConnectionType connectionType, bool isMerchant = true, string storeId = null) + public Task RegisterLightningNodeAsync(string cryptoCode, bool isMerchant = true, string storeId = null) + { + return RegisterLightningNodeAsync(cryptoCode, null, isMerchant, storeId); + } + public async Task RegisterLightningNodeAsync(string cryptoCode, LightningConnectionType? connectionType, bool isMerchant = true, string storeId = null) { var storeController = this.GetController(); - string connectionString = null; - if (connectionType == LightningConnectionType.Charge) - { - if (isMerchant) - connectionString = $"type=charge;server={parent.MerchantCharge.Client.Uri.AbsoluteUri};allowinsecure=true"; - else - throw new NotSupportedException(); - } - else if (connectionType == LightningConnectionType.CLightning) - { - if (isMerchant) - connectionString = "type=clightning;server=" + - ((CLightningClient)parent.MerchantLightningD).Address.AbsoluteUri; - else - connectionString = "type=clightning;server=" + - ((CLightningClient)parent.CustomerLightningD).Address.AbsoluteUri; - } - else if (connectionType == LightningConnectionType.LndREST) - { - if (isMerchant) - connectionString = $"type=lnd-rest;server={parent.MerchantLnd.Swagger.BaseUrl};allowinsecure=true"; - else - throw new NotSupportedException(); - } - else - throw new NotSupportedException(connectionType.ToString()); + string connectionString = parent.GetLightningConnectionString(connectionType, isMerchant); await storeController.AddLightningNode(storeId ?? StoreId, - new LightningNodeViewModel() {ConnectionString = connectionString, SkipPortTest = true}, "save", "BTC"); + new LightningNodeViewModel() { ConnectionString = connectionString, SkipPortTest = true }, "save", "BTC"); if (storeController.ModelState.ErrorCount != 0) Assert.False(true, storeController.ModelState.FirstOrDefault().Value.Errors[0].ErrorMessage); } diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs index ee61be300..c115fef00 100644 --- a/BTCPayServer.Tests/UnitTest1.cs +++ b/BTCPayServer.Tests/UnitTest1.cs @@ -412,7 +412,7 @@ namespace BTCPayServer.Tests var paymentMethodHandlerDictionary = new PaymentMethodHandlerDictionary(new IPaymentMethodHandler[] { new BitcoinLikePaymentHandler(null, networkProvider, null, null, null), - new LightningLikePaymentHandler(null, null, networkProvider, null, null), + new LightningLikePaymentHandler(null, null, networkProvider, null, null, null), }); var entity = new InvoiceEntity(); entity.Networks = networkProvider; @@ -700,7 +700,7 @@ namespace BTCPayServer.Tests var paymentMethodHandlerDictionary = new PaymentMethodHandlerDictionary(new IPaymentMethodHandler[] { new BitcoinLikePaymentHandler(null, networkProvider, null, null, null), - new LightningLikePaymentHandler(null, null, networkProvider, null, null), + new LightningLikePaymentHandler(null, null, networkProvider, null, null, null), }); var entity = new InvoiceEntity(); entity.Networks = networkProvider; @@ -895,7 +895,7 @@ namespace BTCPayServer.Tests await tester.StartAsync(); await tester.EnsureChannelsSetup(); var user = tester.NewAccount(); - await user.GrantAccessAsync(); + await user.GrantAccessAsync(true); await user.RegisterDerivationSchemeAsync("BTC"); await user.RegisterLightningNodeAsync("BTC", LightningConnectionType.CLightning); user.SetNetworkFeeMode(NetworkFeeMode.Never); @@ -945,7 +945,7 @@ namespace BTCPayServer.Tests await tester.StartAsync(); await tester.EnsureChannelsSetup(); var user = tester.NewAccount(); - user.GrantAccess(); + user.GrantAccess(true); var storeController = user.GetController(); Assert.IsType(storeController.UpdateStore()); Assert.IsType(storeController.AddLightningNode(user.StoreId, "BTC")); @@ -1012,7 +1012,7 @@ namespace BTCPayServer.Tests await tester.StartAsync(); await tester.EnsureChannelsSetup(); var user = tester.NewAccount(); - user.GrantAccess(); + user.GrantAccess(true); user.RegisterLightningNode("BTC", type); user.RegisterDerivationScheme("BTC"); @@ -2126,7 +2126,7 @@ namespace BTCPayServer.Tests await tester.StartAsync(); await tester.EnsureChannelsSetup(); var user = tester.NewAccount(); - user.GrantAccess(); + user.GrantAccess(true); user.RegisterDerivationScheme("BTC", ScriptPubKeyType.Segwit); user.RegisterLightningNode("BTC", LightningConnectionType.CLightning); @@ -2184,7 +2184,7 @@ namespace BTCPayServer.Tests await tester.StartAsync(); await tester.EnsureChannelsSetup(); var user = tester.NewAccount(); - user.GrantAccess(); + user.GrantAccess(true); user.RegisterLightningNode("BTC", LightningConnectionType.Charge); var vm = Assert.IsType(Assert .IsType(user.GetController().CheckoutExperience()).Model); @@ -2992,6 +2992,7 @@ namespace BTCPayServer.Tests var fetcher = new RateFetcher(factory); var pairs = provider.GetAll() + .Where(c => c.CryptoCode != "DASH") // ERR_RATE_UNAVAILABLE(bittrex, DASH_BTC) .Select(c => new CurrencyPair(c.CryptoCode, "USD")) .ToHashSet(); @@ -3410,7 +3411,86 @@ namespace BTCPayServer.Tests Assert.False(fn.Seen); } } - + + [Fact(Timeout = TestTimeout)] + [Trait("Integration", "Integration")] + public async Task CanDoLightningInternalNodeMigration() + { + using (var tester = ServerTester.Create(newDb: true)) + { + tester.ActivateLightning(LightningConnectionType.CLightning); + await tester.StartAsync(); + var acc = tester.NewAccount(); + await acc.GrantAccessAsync(true); + await acc.CreateStoreAsync(); + + // Test if legacy DerivationStrategy column is converted to DerivationStrategies + var store = await tester.PayTester.StoreRepository.FindStore(acc.StoreId); + var xpub = "tpubDDmH1briYfZcTDMEc7uMEA5hinzjUTzR9yMC1drxTMeiWyw1VyCqTuzBke6df2sqbfw9QG6wbgTLF5yLjcXsZNaXvJMZLwNEwyvmiFWcLav"; + var derivation = $"{xpub}-[legacy]"; + store.DerivationStrategy = derivation; + await tester.PayTester.StoreRepository.UpdateStore(store); + await RestartMigration(tester); + store = await tester.PayTester.StoreRepository.FindStore(acc.StoreId); + Assert.True(string.IsNullOrEmpty(store.DerivationStrategy)); + var v = (DerivationSchemeSettings)store.GetSupportedPaymentMethods(tester.NetworkProvider).First(); + Assert.Equal(derivation, v.AccountDerivation.ToString()); + Assert.Equal(derivation, v.AccountOriginal.ToString()); + Assert.Equal(xpub, v.SigningKey.ToString()); + Assert.Equal(xpub, v.GetSigningAccountKeySettings().AccountKey.ToString()); + + await acc.RegisterLightningNodeAsync("BTC", LightningConnectionType.CLightning, true); + store = await tester.PayTester.StoreRepository.FindStore(acc.StoreId); + var lnMethod = store.GetSupportedPaymentMethods(tester.NetworkProvider).OfType().First(); + Assert.NotNull(lnMethod.GetExternalLightningUrl()); + await RestartMigration(tester); + + store = await tester.PayTester.StoreRepository.FindStore(acc.StoreId); + lnMethod = store.GetSupportedPaymentMethods(tester.NetworkProvider).OfType().First(); + Assert.Null(lnMethod.GetExternalLightningUrl()); + + // Test if legacy lightning charge settings are converted to LightningConnectionString + store.DerivationStrategies = new JObject() + { + new JProperty("BTC_LightningLike", new JObject() + { + new JProperty("LightningChargeUrl", "http://mycharge.com/"), + new JProperty("Username", "usr"), + new JProperty("Password", "pass"), + new JProperty("CryptoCode", "BTC"), + new JProperty("PaymentId", "someshit"), + }) + }.ToString(); + await tester.PayTester.StoreRepository.UpdateStore(store); + await RestartMigration(tester); + + store = await tester.PayTester.StoreRepository.FindStore(acc.StoreId); + lnMethod = store.GetSupportedPaymentMethods(tester.NetworkProvider).OfType().First(); + Assert.NotNull(lnMethod.GetExternalLightningUrl()); + + var url = lnMethod.GetExternalLightningUrl(); + Assert.Equal(LightningConnectionType.Charge, url.ConnectionType); + Assert.Equal("pass", url.Password); + Assert.Equal("usr", url.Username); + + // Test if lightning connection strings get migrated to internal + store.DerivationStrategies = new JObject() + { + new JProperty("BTC_LightningLike", new JObject() + { + new JProperty("CryptoCode", "BTC"), + new JProperty("LightningConnectionString", tester.PayTester.IntegratedLightning), + }) + }.ToString(); + await tester.PayTester.StoreRepository.UpdateStore(store); + await RestartMigration(tester); + store = await tester.PayTester.StoreRepository.FindStore(acc.StoreId); + lnMethod = store.GetSupportedPaymentMethods(tester.NetworkProvider).OfType().First(); + Assert.True(lnMethod.IsInternalNode); + } + } + + [Fact(Timeout = TestTimeout)] [Trait("Integration", "Integration")] public async Task CanDoInvoiceMigrations() @@ -3424,7 +3504,7 @@ namespace BTCPayServer.Tests await acc.CreateStoreAsync(); await acc.RegisterDerivationSchemeAsync("BTC"); var store = await tester.PayTester.StoreRepository.FindStore(acc.StoreId); - + var blob = store.GetStoreBlob(); var serializer = new Serializer(null); @@ -3445,10 +3525,10 @@ namespace BTCPayServer.Tests new KeyPath("44'/0'/0'").ToString() } }))); - + blob.AdditionalData.Add("networkFeeDisabled", JToken.Parse( serializer.ToString((bool?)true))); - + blob.AdditionalData.Add("onChainMinValue", JToken.Parse( serializer.ToString(new CurrencyValue() { @@ -3461,18 +3541,13 @@ namespace BTCPayServer.Tests Currency = "USD", Value = 5m }.ToString()))); - + store.SetStoreBlob(blob); await tester.PayTester.StoreRepository.UpdateStore(store); - var settings = tester.PayTester.GetService(); - await settings.UpdateSetting(new MigrationSettings()); - var migrationStartupTask = tester.PayTester.GetService().GetServices() - .Single(task => task is MigrationStartupTask); - await migrationStartupTask.ExecuteAsync(); - - + await RestartMigration(tester); + store = await tester.PayTester.StoreRepository.FindStore(acc.StoreId); - + blob = store.GetStoreBlob(); Assert.Empty(blob.AdditionalData); Assert.Single(blob.PaymentMethodCriteria); @@ -3487,7 +3562,16 @@ namespace BTCPayServer.Tests } } - + + private static async Task RestartMigration(ServerTester tester) + { + var settings = tester.PayTester.GetService(); + await settings.UpdateSetting(new MigrationSettings()); + var migrationStartupTask = tester.PayTester.GetService().GetServices() + .Single(task => task is MigrationStartupTask); + await migrationStartupTask.ExecuteAsync(); + } + [Fact(Timeout = TestTimeout)] [Trait("Integration", "Integration")] public async Task EmailSenderTests() diff --git a/BTCPayServer/Controllers/GreenField/LightningNodeApiController.Internal.cs b/BTCPayServer/Controllers/GreenField/LightningNodeApiController.Internal.cs index 3f44e5168..61c339ce6 100644 --- a/BTCPayServer/Controllers/GreenField/LightningNodeApiController.Internal.cs +++ b/BTCPayServer/Controllers/GreenField/LightningNodeApiController.Internal.cs @@ -28,8 +28,9 @@ namespace BTCPayServer.Controllers.GreenField public InternalLightningNodeApiController( BTCPayNetworkProvider btcPayNetworkProvider, BTCPayServerEnvironment btcPayServerEnvironment, CssThemeManager cssThemeManager, LightningClientFactoryService lightningClientFactory, - IOptions lightningNetworkOptions ) : base( - btcPayNetworkProvider, btcPayServerEnvironment, cssThemeManager) + IOptions lightningNetworkOptions, + IAuthorizationService authorizationService) : base( + btcPayNetworkProvider, btcPayServerEnvironment, cssThemeManager, authorizationService) { _btcPayNetworkProvider = btcPayNetworkProvider; _lightningClientFactory = lightningClientFactory; @@ -100,17 +101,17 @@ namespace BTCPayServer.Controllers.GreenField return base.CreateInvoice(cryptoCode, request); } - protected override Task GetLightningClient(string cryptoCode, bool doingAdminThings) + protected override async Task GetLightningClient(string cryptoCode, bool doingAdminThings) { - _lightningNetworkOptions.Value.InternalLightningByCryptoCode.TryGetValue(cryptoCode, - out var internalLightningNode); var network = _btcPayNetworkProvider.GetNetwork(cryptoCode); - if (network == null || !CanUseInternalLightning(doingAdminThings) || internalLightningNode == null) + if (network == null || + !_lightningNetworkOptions.Value.InternalLightningByCryptoCode.TryGetValue(network.CryptoCode, + out var internalLightningNode) || + !await CanUseInternalLightning(doingAdminThings)) { - return Task.FromResult(null); + return null; } - - return Task.FromResult(_lightningClientFactory.Create(internalLightningNode, network)); + return _lightningClientFactory.Create(internalLightningNode, network); } } } diff --git a/BTCPayServer/Controllers/GreenField/LightningNodeApiController.Store.cs b/BTCPayServer/Controllers/GreenField/LightningNodeApiController.Store.cs index b9e84eeec..39f87e3c4 100644 --- a/BTCPayServer/Controllers/GreenField/LightningNodeApiController.Store.cs +++ b/BTCPayServer/Controllers/GreenField/LightningNodeApiController.Store.cs @@ -31,8 +31,9 @@ namespace BTCPayServer.Controllers.GreenField public StoreLightningNodeApiController( IOptions lightningNetworkOptions, LightningClientFactoryService lightningClientFactory, BTCPayNetworkProvider btcPayNetworkProvider, - BTCPayServerEnvironment btcPayServerEnvironment, CssThemeManager cssThemeManager) : base( - btcPayNetworkProvider, btcPayServerEnvironment, cssThemeManager) + BTCPayServerEnvironment btcPayServerEnvironment, CssThemeManager cssThemeManager, + IAuthorizationService authorizationService) : base( + btcPayNetworkProvider, btcPayServerEnvironment, cssThemeManager, authorizationService) { _lightningNetworkOptions = lightningNetworkOptions; _lightningClientFactory = lightningClientFactory; @@ -100,11 +101,10 @@ namespace BTCPayServer.Controllers.GreenField return base.CreateInvoice(cryptoCode, request); } - protected override Task GetLightningClient(string cryptoCode, + protected override async Task GetLightningClient(string cryptoCode, bool doingAdminThings) { - _lightningNetworkOptions.Value.InternalLightningByCryptoCode.TryGetValue(cryptoCode, - out var internalLightningNode); + var network = _btcPayNetworkProvider.GetNetwork(cryptoCode); var store = HttpContext.GetStoreData(); @@ -117,13 +117,20 @@ namespace BTCPayServer.Controllers.GreenField var existing = store.GetSupportedPaymentMethods(_btcPayNetworkProvider) .OfType() .FirstOrDefault(d => d.PaymentId == id); - if (existing == null || (existing.GetLightningUrl().IsInternalNode(internalLightningNode) && - !CanUseInternalLightning(doingAdminThings))) + if (existing == null) + return null; + if (existing.GetExternalLightningUrl() is LightningConnectionString connectionString) { - return Task.FromResult(null); + return _lightningClientFactory.Create(connectionString, network); } - - return Task.FromResult(_lightningClientFactory.Create(existing.GetLightningUrl(), network)); + else if ( + await CanUseInternalLightning(doingAdminThings) && + _lightningNetworkOptions.Value.InternalLightningByCryptoCode.TryGetValue(network.CryptoCode, + out var internalLightningNode)) + { + return _lightningClientFactory.Create(internalLightningNode, network); + } + return null; } } } diff --git a/BTCPayServer/Controllers/GreenField/LightningNodeApiController.cs b/BTCPayServer/Controllers/GreenField/LightningNodeApiController.cs index 42a33bd36..9f24aa570 100644 --- a/BTCPayServer/Controllers/GreenField/LightningNodeApiController.cs +++ b/BTCPayServer/Controllers/GreenField/LightningNodeApiController.cs @@ -2,10 +2,13 @@ using System; using System.Linq; using System.Threading; using System.Threading.Tasks; +using BTCPayServer.Client; using BTCPayServer.Client.Models; using BTCPayServer.HostedServices; using BTCPayServer.Lightning; +using BTCPayServer.Security; using BTCPayServer.Services; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Newtonsoft.Json.Linq; @@ -32,13 +35,16 @@ namespace BTCPayServer.Controllers.GreenField private readonly BTCPayNetworkProvider _btcPayNetworkProvider; private readonly BTCPayServerEnvironment _btcPayServerEnvironment; private readonly CssThemeManager _cssThemeManager; + private readonly IAuthorizationService _authorizationService; protected LightningNodeApiController(BTCPayNetworkProvider btcPayNetworkProvider, - BTCPayServerEnvironment btcPayServerEnvironment, CssThemeManager cssThemeManager) + BTCPayServerEnvironment btcPayServerEnvironment, CssThemeManager cssThemeManager, + IAuthorizationService authorizationService) { _btcPayNetworkProvider = btcPayNetworkProvider; _btcPayServerEnvironment = btcPayServerEnvironment; _cssThemeManager = cssThemeManager; + _authorizationService = authorizationService; } public virtual async Task GetInfo(string cryptoCode) @@ -294,10 +300,11 @@ namespace BTCPayServer.Controllers.GreenField }; } - protected bool CanUseInternalLightning(bool doingAdminThings) + protected async Task CanUseInternalLightning(bool doingAdminThings) { - return (_btcPayServerEnvironment.IsDeveloping || User.IsInRole(Roles.ServerAdmin) || - (_cssThemeManager.AllowLightningInternalNodeForAll && !doingAdminThings)); + return (!doingAdminThings && _cssThemeManager.AllowLightningInternalNodeForAll) || + (await _authorizationService.AuthorizeAsync(User, null, + new PolicyRequirement(Policies.CanUseInternalLightningNode))).Succeeded; } protected abstract Task GetLightningClient(string cryptoCode, bool doingAdminThings); diff --git a/BTCPayServer/Controllers/GreenField/StoreLightningNetworkPaymentMethodsController.cs b/BTCPayServer/Controllers/GreenField/StoreLightningNetworkPaymentMethodsController.cs index ed9687ab7..e7fc926d9 100644 --- a/BTCPayServer/Controllers/GreenField/StoreLightningNetworkPaymentMethodsController.cs +++ b/BTCPayServer/Controllers/GreenField/StoreLightningNetworkPaymentMethodsController.cs @@ -57,7 +57,7 @@ namespace BTCPayServer.Controllers.GreenField .OfType() .Select(paymentMethod => new LightningNetworkPaymentMethodData(paymentMethod.PaymentId.CryptoCode, - paymentMethod.GetLightningUrl().ToString(), !excludedPaymentMethods.Match(paymentMethod.PaymentId))) + paymentMethod.GetExternalLightningUrl().ToString(), !excludedPaymentMethods.Match(paymentMethod.PaymentId))) .Where((result) => !enabledOnly || result.Enabled) .ToList() ); @@ -110,8 +110,6 @@ namespace BTCPayServer.Controllers.GreenField return NotFound(); } - var internalLightning = await GetInternalLightningNode(network.CryptoCode); - if (string.IsNullOrEmpty(paymentMethodData?.ConnectionString)) { ModelState.AddModelError(nameof(LightningNetworkPaymentMethodData.ConnectionString), @@ -124,66 +122,44 @@ namespace BTCPayServer.Controllers.GreenField LightningSupportedPaymentMethod paymentMethod = null; if (!string.IsNullOrEmpty(paymentMethodData.ConnectionString)) { - if (!LightningConnectionString.TryParse(paymentMethodData.ConnectionString, false, - out var connectionString, out var error)) - { - ModelState.AddModelError(nameof(paymentMethodData.ConnectionString), $"Invalid URL ({error})"); - return this.CreateValidationError(ModelState); - } - - if (connectionString.ConnectionType == LightningConnectionType.LndGRPC) - { - ModelState.AddModelError(nameof(paymentMethodData.ConnectionString), - $"BTCPay does not support gRPC connections"); - return this.CreateValidationError(ModelState); - } - - bool isInternalNode = connectionString.IsInternalNode(internalLightning); - - if (connectionString.BaseUri.Scheme == "http") - { - if (!isInternalNode && !connectionString.AllowInsecure) - { - ModelState.AddModelError(nameof(paymentMethodData.ConnectionString), "The url must be HTTPS"); - return this.CreateValidationError(ModelState); - } - } - - if (connectionString.MacaroonFilePath != null) + if (paymentMethodData.ConnectionString == LightningSupportedPaymentMethod.InternalNode) { if (!await CanUseInternalLightning()) { - ModelState.AddModelError(nameof(paymentMethodData.ConnectionString), - "You are not authorized to use macaroonfilepath"); + ModelState.AddModelError(nameof(paymentMethodData.ConnectionString), $"You are not authorized to use the internal lightning node"); return this.CreateValidationError(ModelState); } - - if (!System.IO.File.Exists(connectionString.MacaroonFilePath)) + paymentMethod = new Payments.Lightning.LightningSupportedPaymentMethod() + { + CryptoCode = paymentMethodId.CryptoCode + }; + paymentMethod.SetInternalNode(); + } + else + { + if (!LightningConnectionString.TryParse(paymentMethodData.ConnectionString, false, + out var connectionString, out var error)) + { + ModelState.AddModelError(nameof(paymentMethodData.ConnectionString), $"Invalid URL ({error})"); + return this.CreateValidationError(ModelState); + } + if (connectionString.ConnectionType == LightningConnectionType.LndGRPC) { ModelState.AddModelError(nameof(paymentMethodData.ConnectionString), - "The macaroonfilepath file does not exist"); + $"BTCPay does not support gRPC connections"); return this.CreateValidationError(ModelState); } - - if (!System.IO.Path.IsPathRooted(connectionString.MacaroonFilePath)) + if (!await CanManageServer() && !connectionString.IsSafe()) { - ModelState.AddModelError(nameof(paymentMethodData.ConnectionString), - "The macaroonfilepath should be fully rooted"); + ModelState.AddModelError(nameof(paymentMethodData.ConnectionString), $"You do not have 'btcpay.server.canmodifyserversettings' rights, so the connection string should not contain 'cookiefilepath', 'macaroondirectorypath', 'macaroonfilepath', and should not point to a local ip or to a dns name ending with '.internal', '.local', '.lan' or '.'."); return this.CreateValidationError(ModelState); } + paymentMethod = new Payments.Lightning.LightningSupportedPaymentMethod() + { + CryptoCode = paymentMethodId.CryptoCode + }; + paymentMethod.SetLightningUrl(connectionString); } - - if (isInternalNode && !await CanUseInternalLightning()) - { - ModelState.AddModelError(nameof(paymentMethodData.ConnectionString), "Unauthorized url"); - return this.CreateValidationError(ModelState); - } - - paymentMethod = new Payments.Lightning.LightningSupportedPaymentMethod() - { - CryptoCode = paymentMethodId.CryptoCode - }; - paymentMethod.SetLightningUrl(connectionString); } var store = Store; @@ -209,7 +185,7 @@ namespace BTCPayServer.Controllers.GreenField return paymentMethod == null ? null : new LightningNetworkPaymentMethodData(paymentMethod.PaymentId.CryptoCode, - paymentMethod.GetLightningUrl().ToString(), !excluded); + paymentMethod.GetDisplayableConnectionString(), !excluded); } private bool GetNetwork(string cryptoCode, out BTCPayNetwork network) @@ -219,20 +195,17 @@ namespace BTCPayServer.Controllers.GreenField return network != null; } - private async Task GetInternalLightningNode(string cryptoCode) - { - if (_lightningNetworkOptions.Value.InternalLightningByCryptoCode.TryGetValue(cryptoCode, out var connectionString)) - { - return await CanUseInternalLightning() ? connectionString : null; - } - return null; - } - private async Task CanUseInternalLightning() { return _cssThemeManager.AllowLightningInternalNodeForAll || (await _authorizationService.AuthorizeAsync(User, null, new PolicyRequirement(Policies.CanUseInternalLightningNode))).Succeeded; } + private async Task CanManageServer() + { + return + (await _authorizationService.AuthorizeAsync(User, null, + new PolicyRequirement(Policies.CanModifyServerSettings))).Succeeded; + } } } diff --git a/BTCPayServer/Controllers/StoresController.LightningLike.cs b/BTCPayServer/Controllers/StoresController.LightningLike.cs index 452563a04..c352a1275 100644 --- a/BTCPayServer/Controllers/StoresController.LightningLike.cs +++ b/BTCPayServer/Controllers/StoresController.LightningLike.cs @@ -25,7 +25,6 @@ namespace BTCPayServer.Controllers LightningNodeViewModel vm = new LightningNodeViewModel { CryptoCode = cryptoCode, - InternalLightningNode = GetInternalLighningNode(cryptoCode)?.ToString(), StoreId = storeId }; SetExistingValues(store, vm); @@ -34,8 +33,12 @@ namespace BTCPayServer.Controllers private void SetExistingValues(StoreData store, LightningNodeViewModel vm) { - vm.ConnectionString = GetExistingLightningSupportedPaymentMethod(vm.CryptoCode, store)?.GetLightningUrl()?.ToString(); + if (GetExistingLightningSupportedPaymentMethod(vm.CryptoCode, store) is LightningSupportedPaymentMethod paymentMethod) + { + vm.ConnectionString = paymentMethod.GetDisplayableConnectionString(); + } vm.Enabled = !store.GetStoreBlob().IsExcluded(new PaymentMethodId(vm.CryptoCode, PaymentTypes.LightningLike)); + vm.CanUseInternalNode = CanUseInternalLightning(); } private LightningSupportedPaymentMethod GetExistingLightningSupportedPaymentMethod(string cryptoCode, StoreData store) { @@ -45,16 +48,6 @@ namespace BTCPayServer.Controllers .FirstOrDefault(d => d.PaymentId == id); return existing; } - - private LightningConnectionString GetInternalLighningNode(string cryptoCode) - { - if (_lightningNetworkOptions.Value.InternalLightningByCryptoCode.TryGetValue(cryptoCode, out var connectionString)) - { - return CanUseInternalLightning() ? connectionString : null; - } - return null; - } - [HttpPost] [Route("{storeId}/lightning/{cryptoCode}")] public async Task AddLightningNode(string storeId, LightningNodeViewModel vm, string command, string cryptoCode) @@ -65,8 +58,6 @@ namespace BTCPayServer.Controllers return NotFound(); var network = vm.CryptoCode == null ? null : _ExplorerProvider.GetNetwork(vm.CryptoCode); - var internalLightning = GetInternalLighningNode(network.CryptoCode); - vm.InternalLightningNode = internalLightning?.ToString(); if (network == null) { ModelState.AddModelError(nameof(vm.CryptoCode), "Invalid network"); @@ -75,7 +66,20 @@ namespace BTCPayServer.Controllers PaymentMethodId paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.LightningLike); Payments.Lightning.LightningSupportedPaymentMethod paymentMethod = null; - if (!string.IsNullOrEmpty(vm.ConnectionString)) + if (vm.ConnectionString == LightningSupportedPaymentMethod.InternalNode) + { + if (!CanUseInternalLightning()) + { + ModelState.AddModelError(nameof(vm.ConnectionString), $"You are not authorized to use the internal lightning node"); + return View(vm); + } + paymentMethod = new Payments.Lightning.LightningSupportedPaymentMethod() + { + CryptoCode = paymentMethodId.CryptoCode + }; + paymentMethod.SetInternalNode(); + } + else if (!string.IsNullOrEmpty(vm.ConnectionString)) { if (!LightningConnectionString.TryParse(vm.ConnectionString, false, out var connectionString, out var error)) { @@ -88,40 +92,9 @@ namespace BTCPayServer.Controllers ModelState.AddModelError(nameof(vm.ConnectionString), $"BTCPay does not support gRPC connections"); return View(vm); } - - bool isInternalNode = connectionString.IsInternalNode(internalLightning); - - if (connectionString.BaseUri.Scheme == "http") + if (!User.IsInRole(Roles.ServerAdmin) && !connectionString.IsSafe()) { - if (!isInternalNode && !connectionString.AllowInsecure) - { - ModelState.AddModelError(nameof(vm.ConnectionString), "The url must be HTTPS"); - return View(vm); - } - } - - if (connectionString.MacaroonFilePath != null) - { - if (!CanUseInternalLightning()) - { - ModelState.AddModelError(nameof(vm.ConnectionString), "You are not authorized to use macaroonfilepath"); - return View(vm); - } - if (!System.IO.File.Exists(connectionString.MacaroonFilePath)) - { - ModelState.AddModelError(nameof(vm.ConnectionString), "The macaroonfilepath file does not exist"); - return View(vm); - } - if (!System.IO.Path.IsPathRooted(connectionString.MacaroonFilePath)) - { - ModelState.AddModelError(nameof(vm.ConnectionString), "The macaroonfilepath should be fully rooted"); - return View(vm); - } - } - - if (isInternalNode && !CanUseInternalLightning()) - { - ModelState.AddModelError(nameof(vm.ConnectionString), "Unauthorized url"); + ModelState.AddModelError(nameof(vm.ConnectionString), $"You are not a server admin, so the connection string should not contain 'cookiefilepath', 'macaroondirectorypath', 'macaroonfilepath', and should not point to a local ip or to a dns name ending with '.internal', '.local', '.lan' or '.'."); return View(vm); } @@ -170,10 +143,9 @@ namespace BTCPayServer.Controllers return View(vm); } } - private bool CanUseInternalLightning() { - return (_BTCPayEnv.IsDeveloping || User.IsInRole(Roles.ServerAdmin) || _CssThemeManager.AllowLightningInternalNodeForAll); + return User.IsInRole(Roles.ServerAdmin) || _CssThemeManager.AllowLightningInternalNodeForAll; } } } diff --git a/BTCPayServer/Controllers/StoresController.cs b/BTCPayServer/Controllers/StoresController.cs index ab785745b..69e1ab8d6 100644 --- a/BTCPayServer/Controllers/StoresController.cs +++ b/BTCPayServer/Controllers/StoresController.cs @@ -546,8 +546,8 @@ namespace BTCPayServer.Controllers vm.LightningNodes.Add(new StoreViewModel.LightningNode() { CryptoCode = paymentMethodId.CryptoCode, - Address = lightning?.GetLightningUrl()?.BaseUri.AbsoluteUri ?? string.Empty, - Enabled = !excludeFilters.Match(paymentMethodId) && lightning?.GetLightningUrl() != null + Address = lightning?.GetExternalLightningUrl()?.BaseUri.AbsoluteUri ?? "Internal node", + Enabled = !excludeFilters.Match(paymentMethodId) }); break; } diff --git a/BTCPayServer/Data/StoreDataExtensions.cs b/BTCPayServer/Data/StoreDataExtensions.cs index 367fa13d5..520747884 100644 --- a/BTCPayServer/Data/StoreDataExtensions.cs +++ b/BTCPayServer/Data/StoreDataExtensions.cs @@ -69,14 +69,6 @@ namespace BTCPayServer.Data #pragma warning disable CS0618 bool btcReturned = false; - // Legacy stuff which should go away - if (!string.IsNullOrEmpty(storeData.DerivationStrategy)) - { - btcReturned = true; - yield return DerivationSchemeSettings.Parse(storeData.DerivationStrategy, networks.BTC); - } - - if (!string.IsNullOrEmpty(storeData.DerivationStrategies)) { JObject strategies = JObject.Parse(storeData.DerivationStrategies); @@ -130,11 +122,6 @@ namespace BTCPayServer.Data foreach (var strat in strategies.Properties().ToList()) { var stratId = PaymentMethodId.Parse(strat.Name); - if (stratId.IsBTCOnChain) - { - // Legacy stuff which should go away - storeData.DerivationStrategy = null; - } if (stratId == paymentMethodId) { if (supportedPaymentMethod == null) @@ -149,12 +136,7 @@ namespace BTCPayServer.Data break; } } - - if (!existing && supportedPaymentMethod == null && paymentMethodId.IsBTCOnChain) - { - storeData.DerivationStrategy = null; - } - else if (!existing && supportedPaymentMethod != null) + if (!existing && supportedPaymentMethod != null) strategies.Add(new JProperty(supportedPaymentMethod.PaymentId.ToString(), PaymentMethodExtensions.Serialize(supportedPaymentMethod))); storeData.DerivationStrategies = strategies.ToString(); #pragma warning restore CS0618 diff --git a/BTCPayServer/Extensions.cs b/BTCPayServer/Extensions.cs index d518d9f87..3f56a56c8 100644 --- a/BTCPayServer/Extensions.cs +++ b/BTCPayServer/Extensions.cs @@ -45,13 +45,20 @@ namespace BTCPayServer endpoint = bip21.UnknowParameters.TryGetValue($"{PayjoinClient.BIP21EndpointKey}", out var uri) ? new Uri(uri, UriKind.Absolute) : null; return endpoint != null; } - public static bool IsInternalNode(this LightningConnectionString connectionString, LightningConnectionString internalLightning) - { - var internalDomain = internalLightning?.BaseUri?.DnsSafeHost; - return connectionString.ConnectionType == LightningConnectionType.CLightning || - connectionString.BaseUri.DnsSafeHost == internalDomain || - (internalDomain == "127.0.0.1" || internalDomain == "localhost"); + public static bool IsSafe(this LightningConnectionString connectionString) + { + if (connectionString.CookieFilePath != null || + connectionString.MacaroonDirectoryPath != null || + connectionString.MacaroonFilePath != null) + return false; + + var uri = connectionString.BaseUri; + if (uri.Scheme.Equals("unix", StringComparison.OrdinalIgnoreCase)) + return false; + if (!NBitcoin.Utils.TryParseEndpoint(uri.DnsSafeHost, 80, out var endpoint)) + return false; + return !Extensions.IsLocalNetwork(uri.DnsSafeHost); } public static IQueryable Where(this Microsoft.EntityFrameworkCore.DbSet obj, System.Linq.Expressions.Expression> predicate) where TEntity : class diff --git a/BTCPayServer/Hosting/MigrationStartupTask.cs b/BTCPayServer/Hosting/MigrationStartupTask.cs index 614dc5df8..2dee902a9 100644 --- a/BTCPayServer/Hosting/MigrationStartupTask.cs +++ b/BTCPayServer/Hosting/MigrationStartupTask.cs @@ -11,14 +11,17 @@ using BTCPayServer.Configuration; using BTCPayServer.Data; using BTCPayServer.Logging; using BTCPayServer.Payments; +using BTCPayServer.Payments.Lightning; using BTCPayServer.Services; using BTCPayServer.Services.Stores; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using NBitcoin.DataEncoders; using NBXplorer; using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Serialization; namespace BTCPayServer.Hosting { @@ -29,11 +32,15 @@ namespace BTCPayServer.Hosting private readonly BTCPayNetworkProvider _NetworkProvider; private readonly SettingsRepository _Settings; private readonly UserManager _userManager; + + public IOptions LightningOptions { get; } + public MigrationStartupTask( BTCPayNetworkProvider networkProvider, StoreRepository storeRepository, ApplicationDbContextFactory dbContextFactory, UserManager userManager, + IOptions lightningOptions, SettingsRepository settingsRepository) { _DBContextFactory = dbContextFactory; @@ -41,6 +48,7 @@ namespace BTCPayServer.Hosting _NetworkProvider = networkProvider; _Settings = settingsRepository; _userManager = userManager; + LightningOptions = lightningOptions; } public async Task ExecuteAsync(CancellationToken cancellationToken = default) { @@ -106,6 +114,13 @@ namespace BTCPayServer.Hosting settings.TransitionToStoreBlobAdditionalData = true; await _Settings.UpdateSetting(settings); } + + if (!settings.TransitionInternalNodeConnectionString) + { + await TransitionInternalNodeConnectionString(); + settings.TransitionInternalNodeConnectionString = true; + await _Settings.UpdateSetting(settings); + } } catch (Exception ex) { @@ -114,6 +129,100 @@ namespace BTCPayServer.Hosting } } + private async Task TransitionInternalNodeConnectionString() + { + var nodes = LightningOptions.Value.InternalLightningByCryptoCode.Values.Select(c => c.ToString()).ToHashSet(); + await using var ctx = _DBContextFactory.CreateContext(); + foreach (var store in await ctx.Stores.AsQueryable().ToArrayAsync()) + { +#pragma warning disable CS0618 // Type or member is obsolete + if (!string.IsNullOrEmpty(store.DerivationStrategy)) + { + var noLabel = store.DerivationStrategy.Split('-')[0]; + JObject jObject = new JObject(); + jObject.Add("BTC", new JObject() + { + new JProperty("signingKey", noLabel), + new JProperty("accountDerivation", store.DerivationStrategy), + new JProperty("accountOriginal", store.DerivationStrategy), + new JProperty("accountKeySettings", new JArray() + { + new JObject() + { + new JProperty("accountKey", noLabel) + } + }) + }); + store.DerivationStrategies = jObject.ToString(); + store.DerivationStrategy = null; + } + if (string.IsNullOrEmpty(store.DerivationStrategies)) + continue; + + var strats = JObject.Parse(store.DerivationStrategies); + bool updated = false; + foreach (var prop in strats.Properties().Where(p => p.Name.EndsWith("LightningLike", StringComparison.OrdinalIgnoreCase))) + { + var method = ((JObject)prop.Value); + var lightningCharge = method.Property("LightningChargeUrl", StringComparison.OrdinalIgnoreCase); + var ln = method.Property("LightningConnectionString", StringComparison.OrdinalIgnoreCase); + if (lightningCharge != null) + { + var chargeUrl = lightningCharge.Value.Value(); + var usr = method.Property("Username", StringComparison.OrdinalIgnoreCase)?.Value.Value(); + var pass = method.Property("Password", StringComparison.OrdinalIgnoreCase)?.Value.Value(); + updated = true; + if (chargeUrl != null) + { + var fullUri = new UriBuilder(chargeUrl) + { + UserName = usr, + Password = pass + }.Uri.AbsoluteUri; + var newStr = $"type=charge;server={fullUri};allowinsecure=true"; + if (ln is null) + { + ln = new JProperty("LightningConnectionString", newStr); + method.Add(ln); + } + else + { + ln.Value = newStr; + } + } + foreach (var p in new[] { "Username", "Password", "LightningChargeUrl" }) + method.Property(p, StringComparison.OrdinalIgnoreCase)?.Remove(); + } + + var paymentId = method.Property("PaymentId", StringComparison.OrdinalIgnoreCase); + if (paymentId != null) + { + paymentId.Remove(); + updated = true; + } + + if (ln is null) + continue; + if (nodes.Contains(ln.Value.Value())) + { + updated = true; + ln.Value = null; + if (!(method.Property("InternalNodeRef", StringComparison.OrdinalIgnoreCase) is JProperty internalNode)) + { + internalNode = new JProperty("InternalNodeRef", null); + method.Add(internalNode); + } + internalNode.Value = new JValue(LightningSupportedPaymentMethod.InternalNode); + } + } + + if (updated) + store.DerivationStrategies = strats.ToString(); +#pragma warning restore CS0618 // Type or member is obsolete + } + await ctx.SaveChangesAsync(); + } + private async Task TransitionToStoreBlobAdditionalData() { await using var ctx = _DBContextFactory.CreateContext(); @@ -342,8 +451,8 @@ retry: { foreach (var method in store.GetSupportedPaymentMethods(_NetworkProvider).OfType()) { - var lightning = method.GetLightningUrl(); - if (lightning.IsLegacy) + var lightning = method.GetExternalLightningUrl(); + if (lightning?.IsLegacy is true) { method.SetLightningUrl(lightning); store.SetSupportedPaymentMethod(method); diff --git a/BTCPayServer/Models/StoreViewModels/LightningNodeViewModel.cs b/BTCPayServer/Models/StoreViewModels/LightningNodeViewModel.cs index fc8b086c4..aa99784dd 100644 --- a/BTCPayServer/Models/StoreViewModels/LightningNodeViewModel.cs +++ b/BTCPayServer/Models/StoreViewModels/LightningNodeViewModel.cs @@ -16,7 +16,8 @@ namespace BTCPayServer.Models.StoreViewModels get; set; } - public string InternalLightningNode { get; internal set; } + public bool CanUseInternalNode { get; set; } + public bool SkipPortTest { get; set; } [Display(Name="Lightning enabled")] diff --git a/BTCPayServer/Payments/Lightning/LightningLikePaymentHandler.cs b/BTCPayServer/Payments/Lightning/LightningLikePaymentHandler.cs index 002d1c002..b994cbf7b 100644 --- a/BTCPayServer/Payments/Lightning/LightningLikePaymentHandler.cs +++ b/BTCPayServer/Payments/Lightning/LightningLikePaymentHandler.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; +using BTCPayServer.Configuration; using BTCPayServer.Data; using BTCPayServer.HostedServices; using BTCPayServer.Lightning; @@ -14,6 +15,7 @@ using BTCPayServer.Rating; using BTCPayServer.Services; using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Rates; +using Microsoft.Extensions.Options; using NBitcoin; namespace BTCPayServer.Payments.Lightning @@ -32,16 +34,21 @@ namespace BTCPayServer.Payments.Lightning LightningClientFactoryService lightningClientFactory, BTCPayNetworkProvider networkProvider, SocketFactory socketFactory, - CurrencyNameTable currencyNameTable) + CurrencyNameTable currencyNameTable, + IOptions options) { _Dashboard = dashboard; _lightningClientFactory = lightningClientFactory; _networkProvider = networkProvider; _socketFactory = socketFactory; _currencyNameTable = currencyNameTable; + Options = options; } public override PaymentType PaymentType => PaymentTypes.LightningLike; + + public IOptions Options { get; } + public override async Task CreatePaymentMethodDetails( InvoiceLogs logs, LightningSupportedPaymentMethod supportedPaymentMethod, PaymentMethod paymentMethod, StoreData store, @@ -61,7 +68,7 @@ namespace BTCPayServer.Payments.Lightning { // ignored } - var client = _lightningClientFactory.Create(supportedPaymentMethod.GetLightningUrl(), network); + var client = CreateLightningClient(supportedPaymentMethod, network); var expiry = invoice.ExpirationTime - DateTimeOffset.UtcNow; if (expiry < TimeSpan.Zero) expiry = TimeSpan.FromSeconds(1); @@ -105,7 +112,7 @@ namespace BTCPayServer.Payments.Lightning using (var cts = new CancellationTokenSource(LIGHTNING_TIMEOUT)) { - var client = _lightningClientFactory.Create(supportedPaymentMethod.GetLightningUrl(), network); + var client = CreateLightningClient(supportedPaymentMethod, network); LightningNodeInformation info; try { @@ -135,6 +142,21 @@ namespace BTCPayServer.Payments.Lightning } } + private ILightningClient CreateLightningClient(LightningSupportedPaymentMethod supportedPaymentMethod, BTCPayNetwork network) + { + var external = supportedPaymentMethod.GetExternalLightningUrl(); + if (external != null) + { + return _lightningClientFactory.Create(external, network); + } + else + { + if (!Options.Value.InternalLightningByCryptoCode.TryGetValue(network.CryptoCode, out var connectionString)) + throw new PaymentMethodUnavailableException("No internal node configured"); + return _lightningClientFactory.Create(connectionString, network); + } + } + public async Task TestConnection(NodeInfo nodeInfo, CancellationToken cancellation) { try diff --git a/BTCPayServer/Payments/Lightning/LightningListener.cs b/BTCPayServer/Payments/Lightning/LightningListener.cs index fb2c378d7..af9885001 100644 --- a/BTCPayServer/Payments/Lightning/LightningListener.cs +++ b/BTCPayServer/Payments/Lightning/LightningListener.cs @@ -7,6 +7,7 @@ using System.Threading.Channels; using System.Threading.Tasks; using AngleSharp.Dom.Events; using BTCPayServer.Client.Models; +using BTCPayServer.Configuration; using BTCPayServer.Data; using BTCPayServer.Events; using BTCPayServer.Lightning; @@ -17,6 +18,7 @@ using BTCPayServer.Services.Stores; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using NBXplorer; namespace BTCPayServer.Payments.Lightning @@ -40,7 +42,8 @@ namespace BTCPayServer.Payments.Lightning BTCPayNetworkProvider networkProvider, LightningClientFactoryService lightningClientFactory, LightningLikePaymentHandler lightningLikePaymentHandler, - StoreRepository storeRepository) + StoreRepository storeRepository, + IOptions options) { _Aggregator = aggregator; _InvoiceRepository = invoiceRepository; @@ -49,6 +52,7 @@ namespace BTCPayServer.Payments.Lightning this.lightningClientFactory = lightningClientFactory; _lightningLikePaymentHandler = lightningLikePaymentHandler; _storeRepository = storeRepository; + Options = options; } async Task CheckingInvoice(CancellationToken cancellation) @@ -60,11 +64,11 @@ namespace BTCPayServer.Payments.Lightning { foreach (var listenedInvoice in (await GetListenedInvoices(invoiceId)).Where(i => !i.IsExpired())) { - var instanceListenerKey = (listenedInvoice.Network.CryptoCode, listenedInvoice.SupportedPaymentMethod.GetLightningUrl().ToString()); + var instanceListenerKey = (listenedInvoice.Network.CryptoCode, GetLightningUrl(listenedInvoice.SupportedPaymentMethod).ToString()); if (!_InstanceListeners.TryGetValue(instanceListenerKey, out var instanceListener) || !instanceListener.IsListening) { - instanceListener ??= new LightningInstanceListener(_InvoiceRepository, _Aggregator, listenedInvoice.SupportedPaymentMethod, lightningClientFactory, listenedInvoice.Network); + instanceListener ??= new LightningInstanceListener(_InvoiceRepository, _Aggregator, lightningClientFactory, listenedInvoice.Network, GetLightningUrl(listenedInvoice.SupportedPaymentMethod)); var status = await instanceListener.PollPayment(listenedInvoice, cancellation); if (status is null || status is LightningInvoiceStatus.Paid || @@ -119,7 +123,7 @@ namespace BTCPayServer.Payments.Lightning listenedInvoices.Add(new ListenedInvoice() { Expiration = invoice.ExpirationTime, - Uri = lightningSupportedMethod.GetLightningUrl().BaseUri.AbsoluteUri, + Uri = GetLightningUrl(lightningSupportedMethod).BaseUri.AbsoluteUri, PaymentMethodDetails = lightningMethod, SupportedPaymentMethod = lightningSupportedMethod, PaymentMethod = paymentMethod, @@ -206,7 +210,7 @@ namespace BTCPayServer.Payments.Lightning paymentMethod.Network, prepObj)); var instanceListenerKey = (paymentMethod.Network.CryptoCode, - supportedMethod.GetLightningUrl().ToString()); + GetLightningUrl(supportedMethod).ToString()); if (_InstanceListeners.TryGetValue(instanceListenerKey, out var instanceListener)) { await _InvoiceRepository.NewPaymentDetails(invoice.Id, newPaymentMethodDetails, @@ -215,7 +219,7 @@ namespace BTCPayServer.Payments.Lightning instanceListener.AddListenedInvoice(new ListenedInvoice() { Expiration = invoice.ExpirationTime, - Uri = supportedMethod.GetLightningUrl().BaseUri.AbsoluteUri, + Uri = GetLightningUrl(supportedMethod).BaseUri.AbsoluteUri, PaymentMethodDetails = newPaymentMethodDetails, SupportedPaymentMethod = supportedMethod, PaymentMethod = paymentMethod, @@ -240,6 +244,16 @@ namespace BTCPayServer.Payments.Lightning } + private LightningConnectionString GetLightningUrl(LightningSupportedPaymentMethod supportedMethod) + { + var url = supportedMethod.GetExternalLightningUrl(); + if (url != null) + return url; + if (Options.Value.InternalLightningByCryptoCode.TryGetValue(supportedMethod.CryptoCode, out var conn)) + return conn; + throw new InvalidOperationException($"{supportedMethod.CryptoCode}: The internal lightning node is not set up"); + } + TimeSpan _PollInterval = TimeSpan.FromMinutes(1.0); public TimeSpan PollInterval { @@ -257,6 +271,8 @@ namespace BTCPayServer.Payments.Lightning } } + public IOptions Options { get; } + readonly CancellationTokenSource _Cts = new CancellationTokenSource(); private Timer _ListenPoller; @@ -287,23 +303,26 @@ namespace BTCPayServer.Payments.Lightning public class LightningInstanceListener { - private readonly LightningSupportedPaymentMethod supportedPaymentMethod; private readonly InvoiceRepository invoiceRepository; private readonly EventAggregator _eventAggregator; private readonly BTCPayNetwork network; private readonly LightningClientFactoryService _lightningClientFactory; + public LightningConnectionString ConnectionString { get; } + public LightningInstanceListener(InvoiceRepository invoiceRepository, EventAggregator eventAggregator, - LightningSupportedPaymentMethod supportedPaymentMethod, LightningClientFactoryService lightningClientFactory, - BTCPayNetwork network) + BTCPayNetwork network, + LightningConnectionString connectionString) { - this.supportedPaymentMethod = supportedPaymentMethod; + if (connectionString == null) + throw new ArgumentNullException(nameof(connectionString)); this.invoiceRepository = invoiceRepository; _eventAggregator = eventAggregator; this.network = network; _lightningClientFactory = lightningClientFactory; + ConnectionString = connectionString; } internal bool AddListenedInvoice(ListenedInvoice invoice) { @@ -312,12 +331,12 @@ namespace BTCPayServer.Payments.Lightning internal async Task PollPayment(ListenedInvoice listenedInvoice, CancellationToken cancellation) { - var client = _lightningClientFactory.Create(supportedPaymentMethod.GetLightningUrl(), network); + var client = _lightningClientFactory.Create(ConnectionString, network); LightningInvoice lightningInvoice = await client.GetInvoice(listenedInvoice.PaymentMethodDetails.InvoiceId); if (lightningInvoice?.Status is LightningInvoiceStatus.Paid && await AddPayment(lightningInvoice, listenedInvoice.InvoiceId)) { - Logs.PayServer.LogInformation($"{supportedPaymentMethod.CryptoCode} (Lightning): Payment detected via polling on {listenedInvoice.InvoiceId}"); + Logs.PayServer.LogInformation($"{network.CryptoCode} (Lightning): Payment detected via polling on {listenedInvoice.InvoiceId}"); } return lightningInvoice?.Status; } @@ -335,17 +354,17 @@ namespace BTCPayServer.Payments.Lightning public CancellationTokenSource StopListeningCancellationTokenSource; async Task Listen(CancellationToken cancellation) { - Logs.PayServer.LogInformation($"{supportedPaymentMethod.CryptoCode} (Lightning): Start listening {supportedPaymentMethod.GetLightningUrl().BaseUri}"); + Logs.PayServer.LogInformation($"{network.CryptoCode} (Lightning): Start listening {ConnectionString.BaseUri}"); try { - var lightningClient = _lightningClientFactory.Create(supportedPaymentMethod.GetLightningUrl(), network); + var lightningClient = _lightningClientFactory.Create(ConnectionString, network); using (var session = await lightningClient.Listen(cancellation)) { // Just in case the payment arrived after our last poll but before we listened. await PollAllListenedInvoices(cancellation); if (_ErrorAlreadyLogged) { - Logs.PayServer.LogInformation($"{supportedPaymentMethod.CryptoCode} (Lightning): Could reconnect successfully to {supportedPaymentMethod.GetLightningUrl().BaseUri}"); + Logs.PayServer.LogInformation($"{network.CryptoCode} (Lightning): Could reconnect successfully to {ConnectionString.BaseUri}"); } _ErrorAlreadyLogged = false; while (!_ListenedInvoices.IsEmpty) @@ -361,7 +380,7 @@ namespace BTCPayServer.Payments.Lightning { if (await AddPayment(notification, listenedInvoice.InvoiceId)) { - Logs.PayServer.LogInformation($"{supportedPaymentMethod.CryptoCode} (Lightning): Payment detected via notification ({listenedInvoice.InvoiceId})"); + Logs.PayServer.LogInformation($"{network.CryptoCode} (Lightning): Payment detected via notification ({listenedInvoice.InvoiceId})"); } _ListenedInvoices.TryRemove(notification.Id, out var _); } @@ -376,12 +395,12 @@ namespace BTCPayServer.Payments.Lightning catch (Exception ex) when (!cancellation.IsCancellationRequested && !_ErrorAlreadyLogged) { _ErrorAlreadyLogged = true; - Logs.PayServer.LogError(ex, $"{supportedPaymentMethod.CryptoCode} (Lightning): Error while contacting {supportedPaymentMethod.GetLightningUrl().BaseUri}"); - Logs.PayServer.LogInformation($"{supportedPaymentMethod.CryptoCode} (Lightning): Stop listening {supportedPaymentMethod.GetLightningUrl().BaseUri}"); + Logs.PayServer.LogError(ex, $"{network.CryptoCode} (Lightning): Error while contacting {ConnectionString.BaseUri}"); + Logs.PayServer.LogInformation($"{network.CryptoCode} (Lightning): Stop listening {ConnectionString.BaseUri}"); } catch (OperationCanceledException) when (cancellation.IsCancellationRequested) { } if (_ListenedInvoices.IsEmpty) - Logs.PayServer.LogInformation($"{supportedPaymentMethod.CryptoCode} (Lightning): No more invoice to listen on {supportedPaymentMethod.GetLightningUrl().BaseUri}, releasing the connection."); + Logs.PayServer.LogInformation($"{network.CryptoCode} (Lightning): No more invoice to listen on {ConnectionString.BaseUri}, releasing the connection."); } public DateTimeOffset? LastFullPoll { get; set; } diff --git a/BTCPayServer/Payments/Lightning/LightningSupportedPaymentMethod.cs b/BTCPayServer/Payments/Lightning/LightningSupportedPaymentMethod.cs index b7968e7f5..ac0592af8 100644 --- a/BTCPayServer/Payments/Lightning/LightningSupportedPaymentMethod.cs +++ b/BTCPayServer/Payments/Lightning/LightningSupportedPaymentMethod.cs @@ -1,27 +1,24 @@ using System; +using System.Collections.Generic; using BTCPayServer.Lightning; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; namespace BTCPayServer.Payments.Lightning { public class LightningSupportedPaymentMethod : ISupportedPaymentMethod { + public const string InternalNode = "Internal Node"; public string CryptoCode { get; set; } - [Obsolete("Use Get/SetLightningUrl")] - public string Username { get; set; } - [Obsolete("Use Get/SetLightningUrl")] - public string Password { get; set; } - - // This property MUST be after CryptoCode or else JSON serialization fails + [JsonIgnore] public PaymentMethodId PaymentId => new PaymentMethodId(CryptoCode, PaymentTypes.LightningLike); [Obsolete("Use Get/SetLightningUrl")] - public string LightningChargeUrl { get; set; } - - [Obsolete("Use Get/SetLightningUrl")] + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] public string LightningConnectionString { get; set; } - public LightningConnectionString GetLightningUrl() + public LightningConnectionString GetExternalLightningUrl() { #pragma warning disable CS0618 // Type or member is obsolete if (!string.IsNullOrEmpty(LightningConnectionString)) @@ -33,14 +30,7 @@ namespace BTCPayServer.Payments.Lightning return connectionString; } else - { - var fullUri = new UriBuilder(LightningChargeUrl) { UserName = Username, Password = Password }.Uri.AbsoluteUri; - if (!BTCPayServer.Lightning.LightningConnectionString.TryParse(fullUri, true, out var connectionString, out var error)) - { - throw new FormatException(error); - } - return connectionString; - } + return null; #pragma warning restore CS0618 // Type or member is obsolete } @@ -48,13 +38,42 @@ namespace BTCPayServer.Payments.Lightning { if (connectionString == null) throw new ArgumentNullException(nameof(connectionString)); - #pragma warning disable CS0618 // Type or member is obsolete LightningConnectionString = connectionString.ToString(); - Username = null; - Password = null; - LightningChargeUrl = null; #pragma warning restore CS0618 // Type or member is obsolete } + + public string GetDisplayableConnectionString() + { +#pragma warning disable CS0618 // Type or member is obsolete + if (!string.IsNullOrEmpty(LightningConnectionString) && + BTCPayServer.Lightning.LightningConnectionString.TryParse(LightningConnectionString, false, out var conn)) + return conn.ToString(); +#pragma warning restore CS0618 // Type or member is obsolete + if (InternalNodeRef is string s) + return s; + return "Invalid connection string"; + } + + public void SetInternalNode() + { +#pragma warning disable CS0618 // Type or member is obsolete + LightningConnectionString = null; + InternalNodeRef = InternalNode; +#pragma warning restore CS0618 // Type or member is obsolete + } + + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public string InternalNodeRef { get; set; } + [JsonIgnore] + public bool IsInternalNode + { + get + { +#pragma warning disable CS0618 // Type or member is obsolete + return InternalNodeRef == InternalNode; +#pragma warning restore CS0618 // Type or member is obsolete + } + } } } diff --git a/BTCPayServer/Services/BTCPayServerEnvironment.cs b/BTCPayServer/Services/BTCPayServerEnvironment.cs index 8cf27d74d..7c8265902 100644 --- a/BTCPayServer/Services/BTCPayServerEnvironment.cs +++ b/BTCPayServer/Services/BTCPayServerEnvironment.cs @@ -58,7 +58,7 @@ namespace BTCPayServer.Services { get { - return DevelopmentOverride?? NetworkType == ChainName.Regtest && Environment.IsDevelopment(); + return NetworkType == ChainName.Regtest && Environment.IsDevelopment(); } } @@ -87,7 +87,5 @@ namespace BTCPayServer.Services } return txt.ToString(); } - - public bool? DevelopmentOverride; } } diff --git a/BTCPayServer/Services/MigrationSettings.cs b/BTCPayServer/Services/MigrationSettings.cs index 20ca2a5da..bdc06d426 100644 --- a/BTCPayServer/Services/MigrationSettings.cs +++ b/BTCPayServer/Services/MigrationSettings.cs @@ -11,6 +11,7 @@ namespace BTCPayServer.Services public bool CheckedFirstRun { get; set; } public bool PaymentMethodCriteria { get; set; } public bool TransitionToStoreBlobAdditionalData { get; set; } + public bool TransitionInternalNodeConnectionString { get; set; } public override string ToString() { diff --git a/BTCPayServer/Views/Stores/AddLightningNode.cshtml b/BTCPayServer/Views/Stores/AddLightningNode.cshtml index 0997ed79b..dbf16ef9e 100644 --- a/BTCPayServer/Views/Stores/AddLightningNode.cshtml +++ b/BTCPayServer/Views/Stores/AddLightningNode.cshtml @@ -1,4 +1,4 @@ -@model LightningNodeViewModel +@model LightningNodeViewModel @{ Layout = "../Shared/_NavLayout.cshtml"; ViewData.SetActivePageAndTitle(StoreNavPages.Index, "Add lightning node"); @@ -42,6 +42,14 @@

The connection string encapsulates the configuration for connecting to your lightning node. BTCPay Server currently supports:

    +
  • + Internal node, if you are administrator of the server: +
      +
    • + Internal Node +
    • +
    +
  • c-lightning via TCP or unix domain socket connection:
      @@ -78,9 +86,6 @@
    • type=lnd-rest;server=https://mylnd:8080/;macaroon=abef263adfe...;certthumbprint=abef263adfe...
    • -
    • - type=lnd-rest;server=http://mylnd:8080/;macaroonfilepath=/root/.lnd/admin.macaroon;allowinsecure=true -
    - +
    - + Open Public Node Info Page diff --git a/BTCPayServer/wwwroot/swagger/v1/swagger.template.stores-payment-methods.lightning-network.json b/BTCPayServer/wwwroot/swagger/v1/swagger.template.stores-payment-methods.lightning-network.json index 832d92a80..84c8a8c52 100644 --- a/BTCPayServer/wwwroot/swagger/v1/swagger.template.stores-payment-methods.lightning-network.json +++ b/BTCPayServer/wwwroot/swagger/v1/swagger.template.stores-payment-methods.lightning-network.json @@ -241,7 +241,7 @@ }, "connectionString": { "type": "string", - "description": "The lightning connection string", + "description": "The lightning connection string. Set to 'Internal Node' to use the internal node. (See [this doc](https://github.com/btcpayserver/BTCPayServer.Lightning/blob/master/README.md#examples) for some example)", "example": "type=clightning;server=..." } }