using System; using Microsoft.Extensions.Logging; using Microsoft.EntityFrameworkCore; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using BTCPayServer.Data; using BTCPayServer.Services; using BTCPayServer.Services.Stores; using BTCPayServer.Logging; namespace BTCPayServer.HostedServices { public class MigratorHostedService : BaseAsyncService { private ApplicationDbContextFactory _DBContextFactory; private StoreRepository _StoreRepository; private BTCPayNetworkProvider _NetworkProvider; private SettingsRepository _Settings; public MigratorHostedService( BTCPayNetworkProvider networkProvider, StoreRepository storeRepository, ApplicationDbContextFactory dbContextFactory, SettingsRepository settingsRepository) { _DBContextFactory = dbContextFactory; _StoreRepository = storeRepository; _NetworkProvider = networkProvider; _Settings = settingsRepository; } internal override Task[] InitializeTasks() { return new[] { Update() }; } private async Task Update() { try { var settings = (await _Settings.GetSettingAsync()) ?? new MigrationSettings(); 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); } } catch (Exception ex) { Logs.PayServer.LogError(ex, "Error on the MigratorHostedService"); throw; } } private async Task ConvertConvertWalletKeyPathRoots() { bool save = false; using (var ctx = _DBContextFactory.CreateContext()) { foreach (var store in await ctx.Stores.ToArrayAsync()) { #pragma warning disable CS0618 // Type or member is obsolete _StoreRepository.PrepareEntity(store); var blob = store.GetStoreBlob(); if (blob.WalletKeyPathRoots == null) continue; foreach (var scheme in store.GetSupportedPaymentMethods(_NetworkProvider).OfType()) { if (blob.WalletKeyPathRoots.TryGetValue(scheme.PaymentId.ToString().ToLowerInvariant(), out var root)) { scheme.AccountKeyPath = new NBitcoin.KeyPath(root); store.SetSupportedPaymentMethod(scheme); save = true; } } blob.WalletKeyPathRoots = null; store.SetStoreBlob(blob); #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 ctx.Apps.Where(a => a.AppType == "Crowdfund")) { var settings = app.GetSettings(); #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(); } } private async Task ConvertNetworkFeeProperty() { using (var ctx = _DBContextFactory.CreateContext()) { foreach (var store in await ctx.Stores.ToArrayAsync()) { _StoreRepository.PrepareEntity(store); var blob = store.GetStoreBlob(); #pragma warning disable CS0618 // Type or member is obsolete if (blob.NetworkFeeDisabled != null) { blob.NetworkFeeMode = blob.NetworkFeeDisabled.Value ? NetworkFeeMode.Never : NetworkFeeMode.Always; blob.NetworkFeeDisabled = null; store.SetStoreBlob(blob); } #pragma warning restore CS0618 // Type or member is obsolete } await ctx.SaveChangesAsync(); } } private async Task ConvertMultiplierToSpread() { using (var ctx = _DBContextFactory.CreateContext()) { foreach (var store in await ctx.Stores.ToArrayAsync()) { _StoreRepository.PrepareEntity(store); var blob = store.GetStoreBlob(); #pragma warning disable CS0612 // Type or member is obsolete decimal multiplier = 1.0m; if (blob.RateRules != null && blob.RateRules.Count != 0) { foreach (var rule in blob.RateRules) { multiplier = rule.Apply(null, multiplier); } } blob.RateRules = null; blob.Spread = Math.Min(1.0m, Math.Max(0m, -(multiplier - 1.0m))); store.SetStoreBlob(blob); #pragma warning restore CS0612 // Type or member is obsolete } await ctx.SaveChangesAsync(); } } private Task UnreachableStoreCheck() { return _StoreRepository.CleanUnreachableStores(); } private async Task DeprecatedLightningConnectionStringCheck() { using (var ctx = _DBContextFactory.CreateContext()) { foreach (var store in await ctx.Stores.ToArrayAsync()) { _StoreRepository.PrepareEntity(store); foreach (var method in store.GetSupportedPaymentMethods(_NetworkProvider).OfType()) { var lightning = method.GetLightningUrl(); if (lightning.IsLegacy) { method.SetLightningUrl(lightning); store.SetSupportedPaymentMethod(method); } } } await ctx.SaveChangesAsync(); } } } }