2020-06-28 21:44:35 -05:00
|
|
|
using System;
|
2020-12-28 11:10:53 +01:00
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.IO;
|
2020-06-28 18:00:51 +09:00
|
|
|
using System.Linq;
|
2020-12-28 11:10:53 +01:00
|
|
|
using System.Text;
|
2020-06-28 18:00:51 +09:00
|
|
|
using System.Threading;
|
|
|
|
using System.Threading.Tasks;
|
2020-11-17 13:46:23 +01:00
|
|
|
using BTCPayServer.Abstractions.Contracts;
|
2020-06-28 18:00:51 +09:00
|
|
|
using BTCPayServer.Client.Models;
|
2020-12-28 11:10:53 +01:00
|
|
|
using BTCPayServer.Configuration;
|
2020-06-28 18:00:51 +09:00
|
|
|
using BTCPayServer.Data;
|
|
|
|
using BTCPayServer.Logging;
|
2020-12-29 09:58:35 +01:00
|
|
|
using BTCPayServer.Payments;
|
2020-06-28 18:00:51 +09:00
|
|
|
using BTCPayServer.Services;
|
|
|
|
using BTCPayServer.Services.Stores;
|
2020-12-28 11:10:53 +01:00
|
|
|
using DBriize;
|
|
|
|
using DBriize.Utils;
|
2020-06-28 18:00:51 +09:00
|
|
|
using Microsoft.AspNetCore.Identity;
|
|
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
using Microsoft.Extensions.Logging;
|
2020-12-28 11:10:53 +01:00
|
|
|
using NBitcoin.DataEncoders;
|
2020-12-29 09:58:35 +01:00
|
|
|
using NBXplorer;
|
|
|
|
using Newtonsoft.Json.Linq;
|
2020-06-28 18:00:51 +09:00
|
|
|
|
|
|
|
namespace BTCPayServer.Hosting
|
|
|
|
{
|
|
|
|
public class MigrationStartupTask : IStartupTask
|
|
|
|
{
|
2020-06-28 22:07:48 -05:00
|
|
|
private readonly ApplicationDbContextFactory _DBContextFactory;
|
|
|
|
private readonly StoreRepository _StoreRepository;
|
|
|
|
private readonly BTCPayNetworkProvider _NetworkProvider;
|
|
|
|
private readonly SettingsRepository _Settings;
|
2020-06-28 18:00:51 +09:00
|
|
|
private readonly UserManager<ApplicationUser> _userManager;
|
|
|
|
public MigrationStartupTask(
|
|
|
|
BTCPayNetworkProvider networkProvider,
|
|
|
|
StoreRepository storeRepository,
|
|
|
|
ApplicationDbContextFactory dbContextFactory,
|
|
|
|
UserManager<ApplicationUser> userManager,
|
2020-12-29 09:58:35 +01:00
|
|
|
SettingsRepository settingsRepository)
|
2020-06-28 18:00:51 +09:00
|
|
|
{
|
|
|
|
_DBContextFactory = dbContextFactory;
|
|
|
|
_StoreRepository = storeRepository;
|
|
|
|
_NetworkProvider = networkProvider;
|
|
|
|
_Settings = settingsRepository;
|
|
|
|
_userManager = userManager;
|
|
|
|
}
|
|
|
|
public async Task ExecuteAsync(CancellationToken cancellationToken = default)
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
await Migrate(cancellationToken);
|
|
|
|
var settings = (await _Settings.GetSettingAsync<MigrationSettings>()) ?? new MigrationSettings();
|
2020-12-29 09:58:35 +01:00
|
|
|
if (!settings.PaymentMethodCriteria)
|
|
|
|
{
|
|
|
|
await MigratePaymentMethodCriteria();
|
|
|
|
settings.PaymentMethodCriteria = true;
|
|
|
|
await _Settings.UpdateSetting(settings);
|
|
|
|
}
|
2020-06-28 18:00:51 +09:00
|
|
|
if (!settings.DeprecatedLightningConnectionStringCheck)
|
|
|
|
{
|
|
|
|
await DeprecatedLightningConnectionStringCheck();
|
|
|
|
settings.DeprecatedLightningConnectionStringCheck = true;
|
|
|
|
await _Settings.UpdateSetting(settings);
|
|
|
|
}
|
|
|
|
if (!settings.UnreachableStoreCheck)
|
|
|
|
{
|
|
|
|
await UnreachableStoreCheck();
|
|
|
|
settings.UnreachableStoreCheck = true;
|
|
|
|
await _Settings.UpdateSetting(settings);
|
|
|
|
}
|
|
|
|
if (!settings.ConvertMultiplierToSpread)
|
|
|
|
{
|
|
|
|
await ConvertMultiplierToSpread();
|
|
|
|
settings.ConvertMultiplierToSpread = true;
|
|
|
|
await _Settings.UpdateSetting(settings);
|
|
|
|
}
|
|
|
|
if (!settings.ConvertNetworkFeeProperty)
|
|
|
|
{
|
|
|
|
await ConvertNetworkFeeProperty();
|
|
|
|
settings.ConvertNetworkFeeProperty = true;
|
|
|
|
await _Settings.UpdateSetting(settings);
|
|
|
|
}
|
|
|
|
if (!settings.ConvertCrowdfundOldSettings)
|
|
|
|
{
|
|
|
|
await ConvertCrowdfundOldSettings();
|
|
|
|
settings.ConvertCrowdfundOldSettings = true;
|
|
|
|
await _Settings.UpdateSetting(settings);
|
|
|
|
}
|
|
|
|
if (!settings.ConvertWalletKeyPathRoots)
|
|
|
|
{
|
|
|
|
await ConvertConvertWalletKeyPathRoots();
|
|
|
|
settings.ConvertWalletKeyPathRoots = true;
|
|
|
|
await _Settings.UpdateSetting(settings);
|
|
|
|
}
|
|
|
|
if (!settings.CheckedFirstRun)
|
|
|
|
{
|
|
|
|
var themeSettings = await _Settings.GetSettingAsync<ThemeSettings>() ?? new ThemeSettings();
|
|
|
|
var admin = await _userManager.GetUsersInRoleAsync(Roles.ServerAdmin);
|
|
|
|
themeSettings.FirstRun = admin.Count == 0;
|
|
|
|
await _Settings.UpdateSetting(themeSettings);
|
|
|
|
settings.CheckedFirstRun = true;
|
|
|
|
await _Settings.UpdateSetting(settings);
|
|
|
|
}
|
2020-12-29 09:58:35 +01:00
|
|
|
|
|
|
|
if (!settings.TransitionToStoreBlobAdditionalData)
|
|
|
|
{
|
|
|
|
await TransitionToStoreBlobAdditionalData();
|
|
|
|
settings.TransitionToStoreBlobAdditionalData = true;
|
|
|
|
await _Settings.UpdateSetting(settings);
|
|
|
|
}
|
2020-06-28 18:00:51 +09:00
|
|
|
}
|
|
|
|
catch (Exception ex)
|
|
|
|
{
|
|
|
|
Logs.PayServer.LogError(ex, "Error on the MigrationStartupTask");
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-29 09:58:35 +01:00
|
|
|
private async Task TransitionToStoreBlobAdditionalData()
|
|
|
|
{
|
|
|
|
await using var ctx = _DBContextFactory.CreateContext();
|
2021-01-01 02:01:33 -06:00
|
|
|
foreach (var store in await ctx.Stores.AsQueryable().ToArrayAsync())
|
2020-12-29 09:58:35 +01:00
|
|
|
{
|
|
|
|
var blob = store.GetStoreBlob();
|
|
|
|
blob.AdditionalData.Remove("walletKeyPathRoots");
|
|
|
|
blob.AdditionalData.Remove("onChainMinValue");
|
|
|
|
blob.AdditionalData.Remove("lightningMaxValue");
|
|
|
|
blob.AdditionalData.Remove("networkFeeDisabled");
|
|
|
|
blob.AdditionalData.Remove("rateRules");
|
|
|
|
store.SetStoreBlob(blob);
|
|
|
|
}
|
|
|
|
|
|
|
|
await ctx.SaveChangesAsync();
|
|
|
|
}
|
|
|
|
|
2020-06-28 18:00:51 +09:00
|
|
|
private async Task Migrate(CancellationToken cancellationToken)
|
|
|
|
{
|
|
|
|
using (CancellationTokenSource timeout = new CancellationTokenSource(10_000))
|
|
|
|
using (CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(timeout.Token, cancellationToken))
|
|
|
|
{
|
|
|
|
retry:
|
|
|
|
try
|
|
|
|
{
|
|
|
|
await _DBContextFactory.CreateContext().Database.MigrateAsync();
|
|
|
|
}
|
|
|
|
// Starting up
|
|
|
|
catch when (!cts.Token.IsCancellationRequested)
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
await Task.Delay(1000, cts.Token);
|
|
|
|
}
|
|
|
|
catch { }
|
|
|
|
goto retry;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private async Task ConvertConvertWalletKeyPathRoots()
|
|
|
|
{
|
|
|
|
bool save = false;
|
|
|
|
using (var ctx = _DBContextFactory.CreateContext())
|
|
|
|
{
|
2021-01-01 02:01:33 -06:00
|
|
|
foreach (var store in await ctx.Stores.AsQueryable().ToArrayAsync())
|
2020-06-28 18:00:51 +09:00
|
|
|
{
|
|
|
|
#pragma warning disable CS0618 // Type or member is obsolete
|
|
|
|
var blob = store.GetStoreBlob();
|
2020-12-29 09:58:35 +01:00
|
|
|
|
|
|
|
if (blob.AdditionalData.TryGetValue("walletKeyPathRoots", out var walletKeyPathRootsJToken))
|
2020-06-28 18:00:51 +09:00
|
|
|
{
|
2020-12-29 09:58:35 +01:00
|
|
|
var walletKeyPathRoots = walletKeyPathRootsJToken.ToObject<Dictionary<string, string>>();
|
|
|
|
|
|
|
|
if (!(walletKeyPathRoots?.Any() is true))
|
|
|
|
continue;
|
|
|
|
foreach (var scheme in store.GetSupportedPaymentMethods(_NetworkProvider)
|
|
|
|
.OfType<DerivationSchemeSettings>())
|
2020-06-28 18:00:51 +09:00
|
|
|
{
|
2020-12-29 09:58:35 +01:00
|
|
|
if (walletKeyPathRoots.TryGetValue(scheme.PaymentId.ToString().ToLowerInvariant(),
|
|
|
|
out var root))
|
|
|
|
{
|
|
|
|
scheme.AccountKeyPath = new NBitcoin.KeyPath(root);
|
|
|
|
store.SetSupportedPaymentMethod(scheme);
|
|
|
|
save = true;
|
|
|
|
}
|
2020-06-28 18:00:51 +09:00
|
|
|
}
|
2020-12-29 09:58:35 +01:00
|
|
|
|
|
|
|
blob.AdditionalData.Remove("walletKeyPathRoots");
|
|
|
|
store.SetStoreBlob(blob);
|
2020-06-28 18:00:51 +09:00
|
|
|
}
|
|
|
|
#pragma warning restore CS0618 // Type or member is obsolete
|
|
|
|
}
|
|
|
|
if (save)
|
|
|
|
await ctx.SaveChangesAsync();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private async Task ConvertCrowdfundOldSettings()
|
|
|
|
{
|
|
|
|
using (var ctx = _DBContextFactory.CreateContext())
|
|
|
|
{
|
|
|
|
foreach (var app in await ctx.Apps.Where(a => a.AppType == "Crowdfund").ToArrayAsync())
|
|
|
|
{
|
|
|
|
var settings = app.GetSettings<Services.Apps.CrowdfundSettings>();
|
|
|
|
#pragma warning disable CS0618 // Type or member is obsolete
|
|
|
|
if (settings.UseAllStoreInvoices)
|
|
|
|
#pragma warning restore CS0618 // Type or member is obsolete
|
|
|
|
{
|
|
|
|
app.TagAllInvoices = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
await ctx.SaveChangesAsync();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-29 09:58:35 +01:00
|
|
|
private async Task MigratePaymentMethodCriteria()
|
|
|
|
{
|
|
|
|
using (var ctx = _DBContextFactory.CreateContext())
|
|
|
|
{
|
2021-01-01 02:01:33 -06:00
|
|
|
foreach (var store in await ctx.Stores.AsQueryable().ToArrayAsync())
|
2020-12-29 09:58:35 +01:00
|
|
|
{
|
|
|
|
var blob = store.GetStoreBlob();
|
|
|
|
|
|
|
|
CurrencyValue onChainMinValue = null;
|
|
|
|
CurrencyValue lightningMaxValue = null;
|
|
|
|
if (blob.AdditionalData.TryGetValue("onChainMinValue", out var onChainMinValueJToken))
|
|
|
|
{
|
|
|
|
CurrencyValue.TryParse(onChainMinValueJToken.Value<string>(), out onChainMinValue);
|
|
|
|
blob.AdditionalData.Remove("onChainMinValue");
|
|
|
|
}
|
|
|
|
if (blob.AdditionalData.TryGetValue("lightningMaxValue", out var lightningMaxValueJToken))
|
|
|
|
{
|
|
|
|
CurrencyValue.TryParse(lightningMaxValueJToken.Value<string>(), out lightningMaxValue);
|
|
|
|
blob.AdditionalData.Remove("lightningMaxValue");
|
|
|
|
}
|
|
|
|
blob.PaymentMethodCriteria = store.GetEnabledPaymentIds(_NetworkProvider).Select(paymentMethodId=>
|
|
|
|
{
|
|
|
|
var matchedFromBlob =
|
|
|
|
blob.PaymentMethodCriteria?.SingleOrDefault(criteria => criteria.PaymentMethod == paymentMethodId && criteria.Value != null);
|
|
|
|
return matchedFromBlob switch
|
|
|
|
{
|
|
|
|
null when paymentMethodId.PaymentType == LightningPaymentType.Instance &&
|
|
|
|
lightningMaxValue != null => new PaymentMethodCriteria()
|
|
|
|
{
|
|
|
|
Above = false, PaymentMethod = paymentMethodId, Value = lightningMaxValue
|
|
|
|
},
|
|
|
|
null when paymentMethodId.PaymentType == BitcoinPaymentType.Instance &&
|
|
|
|
onChainMinValue != null => new PaymentMethodCriteria()
|
|
|
|
{
|
|
|
|
Above = true, PaymentMethod = paymentMethodId, Value = onChainMinValue
|
|
|
|
},
|
|
|
|
_ => new PaymentMethodCriteria()
|
|
|
|
{
|
|
|
|
PaymentMethod = paymentMethodId,
|
|
|
|
Above = matchedFromBlob?.Above ?? true,
|
|
|
|
Value = matchedFromBlob?.Value
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}).ToList();
|
|
|
|
|
|
|
|
store.SetStoreBlob(blob);
|
|
|
|
}
|
|
|
|
|
|
|
|
await ctx.SaveChangesAsync();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-28 18:00:51 +09:00
|
|
|
private async Task ConvertNetworkFeeProperty()
|
|
|
|
{
|
|
|
|
using (var ctx = _DBContextFactory.CreateContext())
|
|
|
|
{
|
|
|
|
foreach (var store in await ctx.Stores.AsQueryable().ToArrayAsync())
|
|
|
|
{
|
|
|
|
var blob = store.GetStoreBlob();
|
2020-12-29 09:58:35 +01:00
|
|
|
if (blob.AdditionalData.TryGetValue("networkFeeDisabled", out var networkFeeModeJToken))
|
2020-06-28 18:00:51 +09:00
|
|
|
{
|
2020-12-29 09:58:35 +01:00
|
|
|
var networkFeeMode = networkFeeModeJToken.ToObject<bool?>();
|
|
|
|
if (networkFeeMode != null)
|
|
|
|
{
|
|
|
|
blob.NetworkFeeMode = networkFeeMode.Value ? NetworkFeeMode.Never : NetworkFeeMode.Always;
|
|
|
|
}
|
|
|
|
|
|
|
|
blob.AdditionalData.Remove("networkFeeDisabled");
|
2020-06-28 18:00:51 +09:00
|
|
|
store.SetStoreBlob(blob);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
await ctx.SaveChangesAsync();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private async Task ConvertMultiplierToSpread()
|
|
|
|
{
|
|
|
|
using (var ctx = _DBContextFactory.CreateContext())
|
|
|
|
{
|
2021-01-01 02:01:33 -06:00
|
|
|
foreach (var store in await ctx.Stores.AsQueryable().ToArrayAsync())
|
2020-06-28 18:00:51 +09:00
|
|
|
{
|
|
|
|
var blob = store.GetStoreBlob();
|
|
|
|
decimal multiplier = 1.0m;
|
2020-12-29 09:58:35 +01:00
|
|
|
if (blob.AdditionalData.TryGetValue("rateRules", out var rateRulesJToken))
|
2020-06-28 18:00:51 +09:00
|
|
|
{
|
2020-12-29 09:58:35 +01:00
|
|
|
var rateRules = new Serializer(null).ToObject<List<RateRule_Obsolete>>(rateRulesJToken.ToString());
|
|
|
|
if (rateRules != null && rateRules.Count != 0)
|
2020-06-28 18:00:51 +09:00
|
|
|
{
|
2020-12-29 09:58:35 +01:00
|
|
|
foreach (var rule in rateRules)
|
|
|
|
{
|
|
|
|
multiplier = rule.Apply(null, multiplier);
|
|
|
|
}
|
2020-06-28 18:00:51 +09:00
|
|
|
}
|
2020-12-29 09:58:35 +01:00
|
|
|
|
|
|
|
blob.AdditionalData.Remove("rateRules");
|
|
|
|
blob.Spread = Math.Min(1.0m, Math.Max(0m, -(multiplier - 1.0m)));
|
|
|
|
store.SetStoreBlob(blob);
|
2020-06-28 18:00:51 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
await ctx.SaveChangesAsync();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-29 09:58:35 +01:00
|
|
|
public class RateRule_Obsolete
|
|
|
|
{
|
|
|
|
public RateRule_Obsolete()
|
|
|
|
{
|
|
|
|
RuleName = "Multiplier";
|
|
|
|
}
|
|
|
|
public string RuleName { get; set; }
|
|
|
|
|
|
|
|
public double Multiplier { get; set; }
|
|
|
|
|
|
|
|
public decimal Apply(BTCPayNetworkBase network, decimal rate)
|
|
|
|
{
|
|
|
|
return rate * (decimal)Multiplier;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-28 18:00:51 +09:00
|
|
|
private Task UnreachableStoreCheck()
|
|
|
|
{
|
|
|
|
return _StoreRepository.CleanUnreachableStores();
|
|
|
|
}
|
|
|
|
|
|
|
|
private async Task DeprecatedLightningConnectionStringCheck()
|
|
|
|
{
|
|
|
|
using (var ctx = _DBContextFactory.CreateContext())
|
|
|
|
{
|
2021-01-01 02:01:33 -06:00
|
|
|
foreach (var store in await ctx.Stores.AsQueryable().ToArrayAsync())
|
2020-06-28 18:00:51 +09:00
|
|
|
{
|
|
|
|
foreach (var method in store.GetSupportedPaymentMethods(_NetworkProvider).OfType<Payments.Lightning.LightningSupportedPaymentMethod>())
|
|
|
|
{
|
|
|
|
var lightning = method.GetLightningUrl();
|
|
|
|
if (lightning.IsLegacy)
|
|
|
|
{
|
|
|
|
method.SetLightningUrl(lightning);
|
|
|
|
store.SetSupportedPaymentMethod(method);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
await ctx.SaveChangesAsync();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|