mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-20 13:34:37 +01:00
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
This commit is contained in:
parent
49ae62b02e
commit
7e714f1ef8
24 changed files with 572 additions and 297 deletions
|
@ -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");
|
||||
|
|
|
@ -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<T>()
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -540,7 +540,7 @@ namespace BTCPayServer.Tests
|
|||
}
|
||||
}
|
||||
|
||||
private async Task AssertValidationError(string[] fields, Func<Task> act)
|
||||
private async Task<GreenFieldValidationException> AssertValidationError(string[] fields, Func<Task> act)
|
||||
{
|
||||
var remainingFields = fields.ToHashSet();
|
||||
var ex = await Assert.ThrowsAsync<GreenFieldValidationException>(act);
|
||||
|
@ -550,6 +550,7 @@ namespace BTCPayServer.Tests
|
|||
remainingFields.Remove(field);
|
||||
}
|
||||
Assert.Empty(remainingFields);
|
||||
return ex;
|
||||
}
|
||||
|
||||
private async Task AssertHttpError(int code, Func<Task> 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<BTCPayServerEnvironment>().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<BTCPayServerEnvironment>().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<SettingsRepository>().GetSettingAsync<PoliciesSettings>())?? new PoliciesSettings();
|
||||
|
|
|
@ -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<BTCPayNetwork>("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
|
||||
|
|
|
@ -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<StoresController>();
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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<StoresController>();
|
||||
Assert.IsType<ViewResult>(storeController.UpdateStore());
|
||||
Assert.IsType<ViewResult>(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<CheckoutExperienceViewModel>(Assert
|
||||
.IsType<ViewResult>(user.GetController<StoresController>().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<LightningSupportedPaymentMethod>().First();
|
||||
Assert.NotNull(lnMethod.GetExternalLightningUrl());
|
||||
await RestartMigration(tester);
|
||||
|
||||
store = await tester.PayTester.StoreRepository.FindStore(acc.StoreId);
|
||||
lnMethod = store.GetSupportedPaymentMethods(tester.NetworkProvider).OfType<LightningSupportedPaymentMethod>().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<LightningSupportedPaymentMethod>().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<LightningSupportedPaymentMethod>().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<SettingsRepository>();
|
||||
await settings.UpdateSetting<MigrationSettings>(new MigrationSettings());
|
||||
var migrationStartupTask = tester.PayTester.GetService<IServiceProvider>().GetServices<IStartupTask>()
|
||||
.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<SettingsRepository>();
|
||||
await settings.UpdateSetting<MigrationSettings>(new MigrationSettings());
|
||||
var migrationStartupTask = tester.PayTester.GetService<IServiceProvider>().GetServices<IStartupTask>()
|
||||
.Single(task => task is MigrationStartupTask);
|
||||
await migrationStartupTask.ExecuteAsync();
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task EmailSenderTests()
|
||||
|
|
|
@ -28,8 +28,9 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
public InternalLightningNodeApiController(
|
||||
BTCPayNetworkProvider btcPayNetworkProvider, BTCPayServerEnvironment btcPayServerEnvironment,
|
||||
CssThemeManager cssThemeManager, LightningClientFactoryService lightningClientFactory,
|
||||
IOptions<LightningNetworkOptions> lightningNetworkOptions ) : base(
|
||||
btcPayNetworkProvider, btcPayServerEnvironment, cssThemeManager)
|
||||
IOptions<LightningNetworkOptions> 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<ILightningClient> GetLightningClient(string cryptoCode, bool doingAdminThings)
|
||||
protected override async Task<ILightningClient> GetLightningClient(string cryptoCode, bool doingAdminThings)
|
||||
{
|
||||
_lightningNetworkOptions.Value.InternalLightningByCryptoCode.TryGetValue(cryptoCode,
|
||||
out var internalLightningNode);
|
||||
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(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<ILightningClient>(null);
|
||||
return null;
|
||||
}
|
||||
|
||||
return Task.FromResult(_lightningClientFactory.Create(internalLightningNode, network));
|
||||
return _lightningClientFactory.Create(internalLightningNode, network);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,8 +31,9 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
public StoreLightningNodeApiController(
|
||||
IOptions<LightningNetworkOptions> 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<ILightningClient> GetLightningClient(string cryptoCode,
|
||||
protected override async Task<ILightningClient> GetLightningClient(string cryptoCode,
|
||||
bool doingAdminThings)
|
||||
{
|
||||
_lightningNetworkOptions.Value.InternalLightningByCryptoCode.TryGetValue(cryptoCode,
|
||||
out var internalLightningNode);
|
||||
|
||||
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||
|
||||
var store = HttpContext.GetStoreData();
|
||||
|
@ -117,13 +117,20 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
var existing = store.GetSupportedPaymentMethods(_btcPayNetworkProvider)
|
||||
.OfType<LightningSupportedPaymentMethod>()
|
||||
.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<ILightningClient>(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<IActionResult> GetInfo(string cryptoCode)
|
||||
|
@ -294,10 +300,11 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
};
|
||||
}
|
||||
|
||||
protected bool CanUseInternalLightning(bool doingAdminThings)
|
||||
protected async Task<bool> 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<ILightningClient> GetLightningClient(string cryptoCode, bool doingAdminThings);
|
||||
|
|
|
@ -57,7 +57,7 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
.OfType<LightningSupportedPaymentMethod>()
|
||||
.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<LightningConnectionString> GetInternalLightningNode(string cryptoCode)
|
||||
{
|
||||
if (_lightningNetworkOptions.Value.InternalLightningByCryptoCode.TryGetValue(cryptoCode, out var connectionString))
|
||||
{
|
||||
return await CanUseInternalLightning() ? connectionString : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task<bool> CanUseInternalLightning()
|
||||
{
|
||||
return _cssThemeManager.AllowLightningInternalNodeForAll ||
|
||||
(await _authorizationService.AuthorizeAsync(User, null,
|
||||
new PolicyRequirement(Policies.CanUseInternalLightningNode))).Succeeded;
|
||||
}
|
||||
private async Task<bool> CanManageServer()
|
||||
{
|
||||
return
|
||||
(await _authorizationService.AuthorizeAsync(User, null,
|
||||
new PolicyRequirement(Policies.CanModifyServerSettings))).Succeeded;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<IActionResult> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<TEntity> Where<TEntity>(this Microsoft.EntityFrameworkCore.DbSet<TEntity> obj, System.Linq.Expressions.Expression<Func<TEntity, bool>> predicate) where TEntity : class
|
||||
|
|
|
@ -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<ApplicationUser> _userManager;
|
||||
|
||||
public IOptions<LightningNetworkOptions> LightningOptions { get; }
|
||||
|
||||
public MigrationStartupTask(
|
||||
BTCPayNetworkProvider networkProvider,
|
||||
StoreRepository storeRepository,
|
||||
ApplicationDbContextFactory dbContextFactory,
|
||||
UserManager<ApplicationUser> userManager,
|
||||
IOptions<LightningNetworkOptions> 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<string>();
|
||||
var usr = method.Property("Username", StringComparison.OrdinalIgnoreCase)?.Value.Value<string>();
|
||||
var pass = method.Property("Password", StringComparison.OrdinalIgnoreCase)?.Value.Value<string>();
|
||||
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<string>()))
|
||||
{
|
||||
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<Payments.Lightning.LightningSupportedPaymentMethod>())
|
||||
{
|
||||
var lightning = method.GetLightningUrl();
|
||||
if (lightning.IsLegacy)
|
||||
var lightning = method.GetExternalLightningUrl();
|
||||
if (lightning?.IsLegacy is true)
|
||||
{
|
||||
method.SetLightningUrl(lightning);
|
||||
store.SetSupportedPaymentMethod(method);
|
||||
|
|
|
@ -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")]
|
||||
|
|
|
@ -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<LightningNetworkOptions> options)
|
||||
{
|
||||
_Dashboard = dashboard;
|
||||
_lightningClientFactory = lightningClientFactory;
|
||||
_networkProvider = networkProvider;
|
||||
_socketFactory = socketFactory;
|
||||
_currencyNameTable = currencyNameTable;
|
||||
Options = options;
|
||||
}
|
||||
|
||||
public override PaymentType PaymentType => PaymentTypes.LightningLike;
|
||||
|
||||
public IOptions<LightningNetworkOptions> Options { get; }
|
||||
|
||||
public override async Task<IPaymentMethodDetails> 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
|
||||
|
|
|
@ -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<LightningNetworkOptions> 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<LightningNetworkOptions> 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<LightningInvoiceStatus?> 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; }
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@model LightningNodeViewModel
|
||||
@model LightningNodeViewModel
|
||||
@{
|
||||
Layout = "../Shared/_NavLayout.cshtml";
|
||||
ViewData.SetActivePageAndTitle(StoreNavPages.Index, "Add lightning node");
|
||||
|
@ -42,6 +42,14 @@
|
|||
<div class="form-group">
|
||||
<p class="mb-2">The connection string encapsulates the configuration for connecting to your lightning node. BTCPay Server currently supports:</p>
|
||||
<ul>
|
||||
<li class="mb-2">
|
||||
<strong>Internal node</strong>, if you are administrator of the server:
|
||||
<ul>
|
||||
<li>
|
||||
<code>Internal Node</code>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<strong>c-lightning</strong> via TCP or unix domain socket connection:
|
||||
<ul>
|
||||
|
@ -78,9 +86,6 @@
|
|||
<li>
|
||||
<code><b>type=</b>lnd-rest;<b>server=</b>https://mylnd:8080/;<b>macaroon=</b>abef263adfe...;<b>certthumbprint=</b>abef263adfe...</code>
|
||||
</li>
|
||||
<li>
|
||||
<code><b>type=</b>lnd-rest;<b>server=</b>http://mylnd:8080/;<b>macaroonfilepath=</b>/root/.lnd/admin.macaroon;<b>allowinsecure=</b>true</code>
|
||||
</li>
|
||||
</ul>
|
||||
<a class="d-inline-block my-2 text-secondary text-decoration-none" data-toggle="collapse" href="#lnd-notes" role="button" aria-expanded="false" aria-controls="lnd-notes">
|
||||
<span class="fa fa-question-circle-o" title="More information..."></span> More information on the LND settings
|
||||
|
@ -103,29 +108,28 @@
|
|||
|
||||
<div class="form-group">
|
||||
<label asp-for="ConnectionString"></label>
|
||||
<input asp-for="ConnectionString" class="form-control"/>
|
||||
<input asp-for="ConnectionString" class="form-control" />
|
||||
<span asp-validation-for="ConnectionString" class="text-danger"></span>
|
||||
@if (Model.InternalLightningNode != null)
|
||||
@if (Model.CanUseInternalNode)
|
||||
{
|
||||
<p class="form-text text-muted">
|
||||
Use the internal lightning node of this BTCPay Server instance by
|
||||
<a href="#" id="internal-ln-node-setter" onclick="$('#ConnectionString').val('@Model.InternalLightningNode');return false;">clicking here</a>.
|
||||
<a href="#" id="internal-ln-node-setter" onclick="$('#ConnectionString').val('Internal Node');return false;">clicking here</a>.
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
<div class="form-group form-check">
|
||||
<input asp-for="Enabled" type="checkbox" class="form-check-input"/>
|
||||
<input asp-for="Enabled" type="checkbox" class="form-check-input" />
|
||||
<label asp-for="Enabled" class="form-check-label"></label>
|
||||
</div>
|
||||
<button id="save" name="command" type="submit" value="save" class="btn btn-primary">Submit</button>
|
||||
<button name="command" type="submit" value="test" class="btn btn-secondary mr-3">Test connection</button>
|
||||
<a
|
||||
class="text-secondary"
|
||||
asp-controller="PublicLightningNodeInfo"
|
||||
asp-action="ShowLightningNodeInfo"
|
||||
asp-route-cryptoCode="@Model.CryptoCode"
|
||||
asp-route-storeId="@Model.StoreId"
|
||||
target="_blank">
|
||||
<a class="text-secondary"
|
||||
asp-controller="PublicLightningNodeInfo"
|
||||
asp-action="ShowLightningNodeInfo"
|
||||
asp-route-cryptoCode="@Model.CryptoCode"
|
||||
asp-route-storeId="@Model.StoreId"
|
||||
target="_blank">
|
||||
<span class="fa fa-info-circle" title="More information..."></span>
|
||||
Open Public Node Info Page
|
||||
</a>
|
||||
|
|
|
@ -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=..."
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue