2020-06-28 21:44:35 -05:00
using System ;
2020-12-28 11:10:53 +01:00
using System.Collections.Generic ;
2023-05-23 02:18:57 +02:00
using System.IO ;
2020-06-28 18:00:51 +09:00
using System.Linq ;
using System.Threading ;
using System.Threading.Tasks ;
2020-11-17 13:46:23 +01:00
using BTCPayServer.Abstractions.Contracts ;
2020-12-28 11:10:53 +01:00
using BTCPayServer.Configuration ;
2020-06-28 18:00:51 +09:00
using BTCPayServer.Data ;
2021-04-28 06:14:15 +02:00
using BTCPayServer.Fido2 ;
using BTCPayServer.Fido2.Models ;
2020-12-29 09:58:35 +01:00
using BTCPayServer.Payments ;
2024-04-04 16:31:04 +09:00
using BTCPayServer.Payments.Bitcoin ;
2021-03-02 11:11:58 +09:00
using BTCPayServer.Payments.Lightning ;
2024-04-04 16:31:04 +09:00
using BTCPayServer.PayoutProcessors ;
2024-05-01 10:22:07 +09:00
using BTCPayServer.Payouts ;
2023-03-17 03:56:32 +01:00
using BTCPayServer.Plugins.Crowdfund ;
using BTCPayServer.Plugins.PointOfSale ;
2022-07-18 20:51:53 +02:00
using BTCPayServer.Plugins.PointOfSale.Models ;
2020-06-28 18:00:51 +09:00
using BTCPayServer.Services ;
2021-10-11 12:46:05 +02:00
using BTCPayServer.Services.Apps ;
2024-04-04 16:31:04 +09:00
using BTCPayServer.Services.Invoices ;
2020-06-28 18:00:51 +09:00
using BTCPayServer.Services.Stores ;
2022-12-12 12:28:24 +01:00
using BTCPayServer.Storage.Models ;
using BTCPayServer.Storage.Services.Providers.FileSystemStorage.Configuration ;
2021-04-28 06:14:15 +02:00
using Fido2NetLib.Objects ;
2020-06-28 18:00:51 +09:00
using Microsoft.EntityFrameworkCore ;
using Microsoft.Extensions.Logging ;
2021-03-02 11:11:58 +09:00
using Microsoft.Extensions.Options ;
2022-10-11 17:34:29 +09:00
using Newtonsoft.Json ;
2020-12-29 09:58:35 +01:00
using Newtonsoft.Json.Linq ;
2021-04-28 06:14:15 +02:00
using PeterO.Cbor ;
2023-05-23 02:18:57 +02:00
using YamlDotNet.RepresentationModel ;
2023-01-23 10:11:34 +01:00
using LightningAddressData = BTCPayServer . Data . LightningAddressData ;
2020-06-28 18:00:51 +09:00
namespace BTCPayServer.Hosting
{
public class MigrationStartupTask : IStartupTask
{
2021-11-22 17:16:08 +09:00
2020-06-28 22:07:48 -05:00
private readonly ApplicationDbContextFactory _DBContextFactory ;
private readonly StoreRepository _StoreRepository ;
2024-04-04 16:31:04 +09:00
private readonly PaymentMethodHandlerDictionary _handlers ;
2020-06-28 22:07:48 -05:00
private readonly SettingsRepository _Settings ;
2021-10-11 12:46:05 +02:00
private readonly AppService _appService ;
2024-05-01 10:22:07 +09:00
private readonly PayoutMethodHandlerDictionary _payoutHandlers ;
2021-10-21 17:43:02 +02:00
private readonly BTCPayNetworkJsonSerializerSettings _btcPayNetworkJsonSerializerSettings ;
2022-04-19 09:58:31 +02:00
private readonly LightningAddressService _lightningAddressService ;
2022-04-24 05:19:34 +02:00
private readonly ILogger < MigrationStartupTask > _logger ;
2023-11-21 10:55:02 +01:00
private readonly LightningClientFactoryService _lightningClientFactoryService ;
2024-05-09 02:18:02 +02:00
private readonly IFileService _fileService ;
2021-03-02 11:11:58 +09:00
public IOptions < LightningNetworkOptions > LightningOptions { get ; }
2020-06-28 18:00:51 +09:00
public MigrationStartupTask (
2024-04-04 16:31:04 +09:00
PaymentMethodHandlerDictionary handlers ,
2020-06-28 18:00:51 +09:00
StoreRepository storeRepository ,
ApplicationDbContextFactory dbContextFactory ,
2021-03-02 11:11:58 +09:00
IOptions < LightningNetworkOptions > lightningOptions ,
2021-10-11 12:46:05 +02:00
SettingsRepository settingsRepository ,
2021-12-31 16:59:02 +09:00
AppService appService ,
2024-05-01 10:22:07 +09:00
PayoutMethodHandlerDictionary payoutHandlers ,
2021-11-22 17:16:08 +09:00
BTCPayNetworkJsonSerializerSettings btcPayNetworkJsonSerializerSettings ,
2022-04-19 09:58:31 +02:00
LightningAddressService lightningAddressService ,
2023-11-21 10:55:02 +01:00
ILogger < MigrationStartupTask > logger ,
2024-05-09 02:18:02 +02:00
IFileService fileService ,
2023-11-21 10:55:02 +01:00
LightningClientFactoryService lightningClientFactoryService )
2020-06-28 18:00:51 +09:00
{
2024-04-04 16:31:04 +09:00
_handlers = handlers ;
2020-06-28 18:00:51 +09:00
_DBContextFactory = dbContextFactory ;
_StoreRepository = storeRepository ;
_Settings = settingsRepository ;
2021-10-11 12:46:05 +02:00
_appService = appService ;
2021-10-21 17:43:02 +02:00
_payoutHandlers = payoutHandlers ;
_btcPayNetworkJsonSerializerSettings = btcPayNetworkJsonSerializerSettings ;
2022-04-19 09:58:31 +02:00
_lightningAddressService = lightningAddressService ;
2022-04-24 05:19:34 +02:00
_logger = logger ;
2024-05-09 02:18:02 +02:00
_fileService = fileService ;
2023-11-21 10:55:02 +01:00
_lightningClientFactoryService = lightningClientFactoryService ;
2021-03-02 11:11:58 +09:00
LightningOptions = lightningOptions ;
2020-06-28 18:00:51 +09:00
}
public async Task ExecuteAsync ( CancellationToken cancellationToken = default )
{
try
{
await Migrate ( cancellationToken ) ;
2022-06-29 23:38:44 +09:00
var settings = ( await _Settings . GetSettingAsync < MigrationSettings > ( ) ) ;
if ( settings is null )
{
2022-12-12 12:28:24 +01:00
// If it is null, then it's the first run: let's skip all the migrations by setting flags to true
2022-12-01 19:09:51 +09:00
settings = new MigrationSettings ( ) { MigratedInvoiceTextSearchPages = int . MaxValue , MigratedTransactionLabels = int . MaxValue } ;
2022-06-29 23:38:44 +09:00
foreach ( var prop in settings . GetType ( ) . GetProperties ( ) . Where ( p = > p . CanWrite & & p . PropertyType = = typeof ( bool ) ) )
{
prop . SetValue ( settings , true ) ;
}
2022-12-12 12:28:24 +01:00
// Ensure these checks still get run
settings . FileSystemStorageAsDefault = false ;
2022-06-29 23:38:44 +09:00
await _Settings . UpdateSetting ( settings ) ;
2024-04-25 14:09:21 +09:00
await _Settings . UpdateSetting ( new ThemeSettings ( ) ) ;
2022-06-29 23:38:44 +09:00
}
2020-12-29 09:58:35 +01:00
if ( ! settings . PaymentMethodCriteria )
{
await MigratePaymentMethodCriteria ( ) ;
settings . PaymentMethodCriteria = true ;
await _Settings . UpdateSetting ( settings ) ;
}
if ( ! settings . TransitionToStoreBlobAdditionalData )
{
await TransitionToStoreBlobAdditionalData ( ) ;
settings . TransitionToStoreBlobAdditionalData = true ;
await _Settings . UpdateSetting ( settings ) ;
}
2021-03-02 11:11:58 +09:00
if ( ! settings . TransitionInternalNodeConnectionString )
{
await TransitionInternalNodeConnectionString ( ) ;
settings . TransitionInternalNodeConnectionString = true ;
await _Settings . UpdateSetting ( settings ) ;
}
2021-04-28 06:14:15 +02:00
2021-04-28 06:26:59 +02:00
if ( ! settings . MigrateU2FToFIDO2 )
2021-04-28 06:14:15 +02:00
{
await MigrateU2FToFIDO2 ( ) ;
settings . MigrateU2FToFIDO2 = true ;
await _Settings . UpdateSetting ( settings ) ;
}
2021-06-17 15:36:22 +09:00
if ( ! settings . MigrateHotwalletProperty )
{
await MigrateHotwalletProperty ( ) ;
settings . MigrateHotwalletProperty = true ;
await _Settings . UpdateSetting ( settings ) ;
}
2021-10-11 12:46:05 +02:00
if ( ! settings . MigrateAppCustomOption )
{
await MigrateAppCustomOption ( ) ;
settings . MigrateAppCustomOption = true ;
await _Settings . UpdateSetting ( settings ) ;
}
2021-10-21 17:43:02 +02:00
if ( ! settings . MigratePayoutDestinationId )
{
await MigratePayoutDestinationId ( ) ;
settings . MigratePayoutDestinationId = true ;
await _Settings . UpdateSetting ( settings ) ;
}
2021-11-26 16:13:41 +02:00
if ( ! settings . AddInitialUserBlob )
{
await AddInitialUserBlob ( ) ;
settings . AddInitialUserBlob = true ;
await _Settings . UpdateSetting ( settings ) ;
}
2022-01-27 17:06:02 +01:00
if ( ! settings . LighingAddressSettingRename )
{
await MigrateLighingAddressSettingRename ( ) ;
settings . LighingAddressSettingRename = true ;
await _Settings . UpdateSetting ( settings ) ;
}
2022-04-19 09:58:31 +02:00
if ( ! settings . LighingAddressDatabaseMigration )
{
await MigrateLighingAddressDatabaseMigration ( ) ;
settings . LighingAddressDatabaseMigration = true ;
2022-04-24 05:19:34 +02:00
}
if ( ! settings . AddStoreToPayout )
{
await MigrateAddStoreToPayout ( ) ;
settings . AddStoreToPayout = true ;
2022-04-19 09:58:31 +02:00
await _Settings . UpdateSetting ( settings ) ;
}
2022-06-29 23:38:44 +09:00
if ( ! settings . MigrateEmailServerDisableTLSCerts )
{
await MigrateEmailServerDisableTLSCerts ( ) ;
settings . MigrateEmailServerDisableTLSCerts = true ;
await _Settings . UpdateSetting ( settings ) ;
}
2022-10-11 17:34:29 +09:00
if ( ! settings . MigrateWalletColors )
{
await MigrateMigrateLabels ( ) ;
settings . MigrateWalletColors = true ;
await _Settings . UpdateSetting ( settings ) ;
}
2022-12-12 12:28:24 +01:00
if ( ! settings . FileSystemStorageAsDefault )
{
var storageSettings = await _Settings . GetSettingAsync < StorageSettings > ( ) ;
if ( storageSettings is null )
{
storageSettings = new StorageSettings
{
Provider = StorageProvider . FileSystem ,
Configuration = JObject . FromObject ( new FileSystemStorageConfiguration ( ) )
} ;
await _Settings . UpdateSetting ( storageSettings ) ;
}
settings . FileSystemStorageAsDefault = true ;
await _Settings . UpdateSetting ( settings ) ;
}
2023-04-17 10:34:41 +09:00
if ( ! settings . FixMappedDomainAppType )
{
await FixMappedDomainAppType ( ) ;
settings . FixMappedDomainAppType = true ;
2023-05-23 02:18:57 +02:00
}
if ( ! settings . MigrateAppYmlToJson )
{
await MigrateAppYmlToJson ( ) ;
settings . MigrateAppYmlToJson = true ;
2023-04-17 10:34:41 +09:00
await _Settings . UpdateSetting ( settings ) ;
}
2024-04-04 16:31:04 +09:00
if ( ! settings . MigrateToStoreConfig )
{
await MigrateToStoreConfig ( ) ;
settings . MigrateToStoreConfig = true ;
await _Settings . UpdateSetting ( settings ) ;
}
2024-10-04 16:58:13 +09:00
if ( ! settings . MigrateBlockExplorerLinks )
{
await MigrateBlockExplorerLinks ( ) ;
settings . MigrateBlockExplorerLinks = true ;
await _Settings . UpdateSetting ( settings ) ;
}
2024-10-08 16:21:44 +09:00
if ( ! settings . MigrateStoreExcludedPaymentMethods )
{
await MigrateStoreExcludedPaymentMethods ( ) ;
settings . MigrateStoreExcludedPaymentMethods = true ;
await _Settings . UpdateSetting ( settings ) ;
}
2020-06-28 18:00:51 +09:00
}
catch ( Exception ex )
{
2022-04-24 05:19:34 +02:00
_logger . LogError ( ex , "Error on the MigrationStartupTask" ) ;
2020-06-28 18:00:51 +09:00
throw ;
}
}
2021-12-31 16:59:02 +09:00
2024-10-08 16:21:44 +09:00
private async Task MigrateStoreExcludedPaymentMethods ( )
{
await using var ctx = _DBContextFactory . CreateContext ( ) ;
var stores = await ctx . Stores . ToArrayAsync ( ) ;
foreach ( var store in stores )
{
var blob = JObject . Parse ( store . StoreBlob ) ;
var array = blob [ "excludedPaymentMethods" ] as JArray ;
if ( array is null | | array . Count = = 0 )
continue ;
var newArray = new JArray ( array . Select ( a = > MigrationExtensions . MigratePaymentMethodId ( a . Value < string > ( ) ) )
. ToArray ( ) ) ;
if ( array . ToString ( ) = = newArray . ToString ( ) )
continue ;
blob [ "excludedPaymentMethods" ] = newArray ;
2024-10-08 16:24:35 +09:00
store . StoreBlob = blob . ToString ( ) ;
2024-10-08 16:21:44 +09:00
}
2024-10-08 16:24:35 +09:00
await ctx . SaveChangesAsync ( ) ;
2024-10-08 16:21:44 +09:00
}
2024-10-04 16:58:13 +09:00
private async Task MigrateBlockExplorerLinks ( )
{
await using var ctx = _DBContextFactory . CreateContext ( ) ;
var settings = await ctx . Settings . Where ( s = > s . Id = = "BTCPayServer.Services.PoliciesSettings" ) . FirstOrDefaultAsync ( ) ;
if ( settings is null )
return ;
var obj = JObject . Parse ( settings . Value ) ;
var arr = obj [ "BlockExplorerLinks" ] as JArray ;
if ( arr is null or { Count : 0 } )
return ;
foreach ( var item in arr . OfType < JObject > ( ) )
{
var cryptoCode = item [ "CryptoCode" ] ? . Value < string > ( ) ;
if ( cryptoCode is null )
continue ;
item . Remove ( "CryptoCode" ) ;
item [ "PaymentMethodId" ] = PaymentTypes . CHAIN . GetPaymentMethodId ( cryptoCode ) . ToString ( ) ;
}
settings . Value = obj . ToString ( ) ;
await ctx . SaveChangesAsync ( ) ;
}
2024-04-04 16:31:04 +09:00
private async Task MigrateToStoreConfig ( )
{
await using var ctx = _DBContextFactory . CreateContext ( ) ;
var stores = await ctx . Stores . ToArrayAsync ( ) ;
foreach ( var store in stores )
{
if ( string . IsNullOrEmpty ( store . DerivationStrategies ) )
continue ;
var strategies = JObject . Parse ( store . DerivationStrategies ) ;
foreach ( var s in strategies . Properties ( ) . ToList ( ) )
{
var ls = s ;
var pmi = PaymentMethodId . Parse ( s . Name ) ;
MigrationExtensions . RenameProperty ( ref ls , pmi . ToString ( ) ) ;
if ( ls . Value is JObject conf )
{
if ( IsLN ( pmi ) )
{
conf . RenameProperty ( "LightningConnectionString" , "connectionString" ) ;
conf . Remove ( "DisableBOLT11PaymentOption" ) ; // Old
conf . RenameProperty ( "InternalNodeRef" , "internalNodeRef" ) ;
conf . Remove ( "CryptoCode" ) ;
conf . RemoveIfValue ( "internalNodeRef" , null as string ) ;
conf . RemoveIfValue ( "connectionString" , null as string ) ;
}
else if ( IsLNURL ( pmi ) )
{
conf . RenameProperty ( "LUD12Enabled" , "lud12Enabled" ) ;
conf . RenameProperty ( "UseBech32Scheme" , "useBech32Scheme" ) ;
conf . RemoveIfValue ( "lud12Enabled" , false ) ;
conf . RemoveIfValue ( "useBech32Scheme" , false ) ;
conf . Remove ( "CryptoCode" ) ;
}
else if ( IsChain ( pmi ) )
{
conf . RemoveIfValue ( "label" , null as string ) ;
conf . RemoveIfValue ( "isHotWallet" , false ) ;
}
}
}
store . DerivationStrategies = strategies . ToString ( Formatting . None ) ;
}
await ctx . SaveChangesAsync ( ) ;
}
2023-04-17 10:34:41 +09:00
private async Task FixMappedDomainAppType ( )
{
await using var ctx = _DBContextFactory . CreateContext ( ) ;
var setting = await ctx . Settings . FirstOrDefaultAsync ( s = > s . Id = = "BTCPayServer.Services.PoliciesSettings" ) ;
if ( setting ? . Value is null )
return ;
string MapToString ( int v )
{
return v switch
{
0 = > "PointOfSale" ,
1 = > "Crowdfund" ,
_ = > throw new NotSupportedException ( )
} ;
}
var data = JObject . Parse ( setting . Value ) ;
if ( data [ "RootAppType" ] ? . Type is JTokenType . Integer )
{
var v = data [ "RootAppType" ] . Value < int > ( ) ;
data [ "RootAppType" ] = new JValue ( MapToString ( v ) ) ;
}
var arr = data [ "DomainToAppMapping" ] as JArray ;
if ( arr ! = null )
{
foreach ( var map in arr )
{
if ( map [ "AppType" ] ? . Type is JTokenType . Integer )
{
var v = map [ "AppType" ] . Value < int > ( ) ;
map [ "AppType" ] = new JValue ( MapToString ( v ) ) ;
}
}
}
setting . Value = data . ToString ( ) ;
await ctx . SaveChangesAsync ( ) ;
}
2024-04-15 19:08:25 +09:00
static async Task < string > GetMigrationState ( ApplicationDbContext postgresContext )
{
var o = ( await postgresContext . Settings . FromSqlRaw ( "SELECT \"Id\", \"Value\" FROM \"Settings\" WHERE \"Id\"='MigrationData'" ) . AsNoTracking ( ) . FirstOrDefaultAsync ( ) ) ? . Value ;
if ( o is null )
return null ;
return JObject . Parse ( o ) [ "state" ] ? . Value < string > ( ) ;
}
static async Task UpdateSequenceInvoiceSearch ( ApplicationDbContext postgresContext )
{
await postgresContext . Database . ExecuteSqlRawAsync ( "SELECT SETVAL('\"InvoiceSearches_Id_seq\"', (SELECT max(\"Id\") FROM \"InvoiceSearches\"));" ) ;
2023-03-07 10:26:47 +09:00
}
2023-05-23 02:18:57 +02:00
private async Task MigrateAppYmlToJson ( )
{
await using var ctx = _DBContextFactory . CreateContext ( ) ;
2024-10-04 16:58:13 +09:00
var apps = await ctx . Apps . Where ( data = > CrowdfundAppType . AppType = = data . AppType | | PointOfSaleAppType . AppType = = data . AppType )
2023-05-23 02:18:57 +02:00
. ToListAsync ( ) ;
2024-10-04 16:58:13 +09:00
foreach ( var app in apps )
2023-05-23 02:18:57 +02:00
{
switch ( app . AppType )
{
2024-10-04 16:58:13 +09:00
case CrowdfundAppType . AppType :
var cfSettings = app . GetSettings < CrowdfundSettings > ( ) ;
if ( ! string . IsNullOrEmpty ( cfSettings ? . PerksTemplate ) )
{
cfSettings . PerksTemplate = AppService . SerializeTemplate ( ParsePOSYML ( cfSettings ? . PerksTemplate ) ) ;
app . SetSettings ( cfSettings ) ;
}
break ;
case PointOfSaleAppType . AppType :
var pSettings = app . GetSettings < PointOfSaleSettings > ( ) ;
if ( ! string . IsNullOrEmpty ( pSettings ? . Template ) )
{
pSettings . Template = AppService . SerializeTemplate ( ParsePOSYML ( pSettings ? . Template ) ) ;
app . SetSettings ( pSettings ) ;
}
break ;
2023-05-23 02:18:57 +02:00
}
}
await ctx . SaveChangesAsync ( ) ;
2024-10-04 16:58:13 +09:00
2023-05-23 02:18:57 +02:00
}
public static ViewPointOfSaleViewModel . Item [ ] ParsePOSYML ( string yaml )
{
var items = new List < ViewPointOfSaleViewModel . Item > ( ) ;
var stream = new YamlStream ( ) ;
2023-07-13 14:59:18 +02:00
if ( string . IsNullOrEmpty ( yaml ) )
return items . ToArray ( ) ;
2024-10-04 16:58:13 +09:00
2023-05-23 02:18:57 +02:00
stream . Load ( new StringReader ( yaml ) ) ;
2024-10-04 16:58:13 +09:00
if ( stream . Documents . FirstOrDefault ( ) ? . RootNode is not YamlMappingNode root )
2023-07-13 14:59:18 +02:00
return items . ToArray ( ) ;
2023-05-23 02:18:57 +02:00
foreach ( var posItem in root . Children )
{
var trimmedKey = ( ( YamlScalarNode ) posItem . Key ) . Value ? . Trim ( ) ;
if ( string . IsNullOrEmpty ( trimmedKey ) )
{
continue ;
}
var currentItem = new ViewPointOfSaleViewModel . Item
{
2024-10-04 16:58:13 +09:00
Id = trimmedKey ,
Title = trimmedKey ,
PriceType = ViewPointOfSaleViewModel . ItemPriceType . Fixed
2023-05-23 02:18:57 +02:00
} ;
var itemSpecs = ( YamlMappingNode ) posItem . Value ;
foreach ( var spec in itemSpecs )
{
2024-10-04 16:58:13 +09:00
if ( spec . Key is not YamlScalarNode { Value : string keyString } | | string . IsNullOrEmpty ( keyString ) )
2023-05-23 02:18:57 +02:00
continue ;
var scalarValue = spec . Value as YamlScalarNode ;
switch ( keyString )
{
case "title" :
currentItem . Title = scalarValue ? . Value ? ? trimmedKey ;
break ;
case "inventory" :
if ( int . TryParse ( scalarValue ? . Value , out var inv ) )
{
currentItem . Inventory = inv ;
}
break ;
case "description" :
currentItem . Description = scalarValue ? . Value ;
break ;
case "image" :
currentItem . Image = scalarValue ? . Value ;
break ;
case "payment_methods" when spec . Value is YamlSequenceNode pmSequenceNode :
currentItem . PaymentMethods = pmSequenceNode . Children
. Select ( node = > ( node as YamlScalarNode ) ? . Value ? . Trim ( ) )
. Where ( node = > ! string . IsNullOrEmpty ( node ) ) . ToArray ( ) ;
break ;
case "price_type" :
case "custom" :
if ( bool . TryParse ( scalarValue ? . Value , out var customBoolValue ) )
{
if ( customBoolValue )
{
currentItem . PriceType = currentItem . Price is null or 0
? ViewPointOfSaleViewModel . ItemPriceType . Topup
: ViewPointOfSaleViewModel . ItemPriceType . Minimum ;
}
else
{
currentItem . PriceType = ViewPointOfSaleViewModel . ItemPriceType . Fixed ;
}
}
else if ( Enum . TryParse < ViewPointOfSaleViewModel . ItemPriceType > ( scalarValue ? . Value , true ,
out var customPriceType ) )
{
currentItem . PriceType = customPriceType ;
}
break ;
case "price" :
if ( decimal . TryParse ( scalarValue ? . Value , out var price ) )
{
currentItem . Price = price ;
}
break ;
case "buybuttontext" :
currentItem . BuyButtonText = scalarValue ? . Value ;
break ;
case "disabled" :
if ( bool . TryParse ( scalarValue ? . Value , out var disabled ) )
{
currentItem . Disabled = disabled ;
}
break ;
}
}
items . Add ( currentItem ) ;
}
return items . ToArray ( ) ;
}
2023-03-07 10:26:47 +09:00
2022-10-11 17:34:29 +09:00
#pragma warning disable CS0612 // Type or member is obsolete
static WalletBlobInfo GetBlobInfo ( WalletData walletData )
{
if ( walletData . Blob = = null | | walletData . Blob . Length = = 0 )
{
return new WalletBlobInfo ( ) ;
}
var blobInfo = JsonConvert . DeserializeObject < WalletBlobInfo > ( ZipUtils . Unzip ( walletData . Blob ) ) ;
return blobInfo ;
}
private async Task MigrateMigrateLabels ( )
{
await using var ctx = _DBContextFactory . CreateContext ( ) ;
var wallets = await ctx . Wallets . AsNoTracking ( ) . ToArrayAsync ( ) ;
foreach ( var wallet in wallets )
{
var blob = GetBlobInfo ( wallet ) ;
HashSet < string > labels = new HashSet < string > ( blob . LabelColors . Count ) ;
foreach ( var label in blob . LabelColors )
{
var labelId = label . Key ;
if ( labelId . StartsWith ( "{" , StringComparison . OrdinalIgnoreCase ) )
{
try
{
labelId = JObject . Parse ( label . Key ) [ "value" ] . Value < string > ( ) ;
}
catch
{
}
}
if ( ! labels . Add ( labelId ) )
continue ;
var obj = new JObject ( ) ;
obj . Add ( "color" , label . Value ) ;
ctx . WalletObjects . Add ( new WalletObjectData ( )
{
WalletId = wallet . Id ,
Type = WalletObjectData . Types . Label ,
Id = labelId ,
Data = obj . ToString ( )
} ) ;
}
}
await ctx . SaveChangesAsync ( ) ;
}
#pragma warning restore CS0612 // Type or member is obsolete
2022-06-29 23:38:44 +09:00
// In the past, if a server was considered local network, then we would disable TLS checks.
// Now we don't do it anymore, as we have an explicit flag (DisableCertificateCheck) to control the behavior.
// But we need to migrate old users that relied on the behavior before.
private async Task MigrateEmailServerDisableTLSCerts ( )
{
await using var ctx = _DBContextFactory . CreateContext ( ) ;
var serverEmailSettings = await _Settings . GetSettingAsync < Services . Mails . EmailSettings > ( ) ;
if ( serverEmailSettings ? . Server is String server )
{
serverEmailSettings . DisableCertificateCheck = Extensions . IsLocalNetwork ( server ) ;
if ( serverEmailSettings . DisableCertificateCheck )
await _Settings . UpdateSetting ( serverEmailSettings ) ;
}
var stores = await ctx . Stores . ToArrayAsync ( ) ;
foreach ( var store in stores )
{
var storeBlob = store . GetStoreBlob ( ) ;
if ( storeBlob . EmailSettings ? . Server is String storeServer )
{
storeBlob . EmailSettings . DisableCertificateCheck = Extensions . IsLocalNetwork ( storeServer ) ;
if ( storeBlob . EmailSettings . DisableCertificateCheck )
store . SetStoreBlob ( storeBlob ) ;
}
}
await ctx . SaveChangesAsync ( ) ;
}
2022-04-19 09:58:31 +02:00
private async Task MigrateLighingAddressDatabaseMigration ( )
{
await using var ctx = _DBContextFactory . CreateContext ( ) ;
var lightningAddressSettings =
await _Settings . GetSettingAsync < UILNURLController . LightningAddressSettings > (
nameof ( UILNURLController . LightningAddressSettings ) ) ;
if ( lightningAddressSettings is null )
{
return ;
}
var storeids = lightningAddressSettings . StoreToItemMap . Keys . ToArray ( ) ;
2023-01-06 14:18:07 +01:00
var existingStores = ( await ctx . Stores . Where ( data = > storeids . Contains ( data . Id ) ) . Select ( data = > data . Id ) . ToArrayAsync ( ) ) . ToHashSet ( ) ;
2022-04-19 09:58:31 +02:00
foreach ( var storeMap in lightningAddressSettings . StoreToItemMap )
{
2023-01-06 14:18:07 +01:00
if ( ! existingStores . Contains ( storeMap . Key ) )
continue ;
2022-04-19 09:58:31 +02:00
foreach ( var storeitem in storeMap . Value )
{
if ( lightningAddressSettings . Items . TryGetValue ( storeitem , out var val ) )
{
await _lightningAddressService . Set (
new LightningAddressData ( )
{
StoreDataId = storeMap . Key ,
2023-02-21 15:06:34 +09:00
Username = storeitem
}
. SetBlob ( new LightningAddressDataBlob ( )
{
Max = val . Max ,
Min = val . Min ,
CurrencyCode = val . CurrencyCode
} ) , ctx ) ;
2022-04-19 09:58:31 +02:00
}
}
}
await ctx . SaveChangesAsync ( ) ;
await _Settings . UpdateSetting < object > ( null , nameof ( UILNURLController . LightningAddressSettings ) ) ;
}
2022-01-27 17:06:02 +01:00
private async Task MigrateLighingAddressSettingRename ( )
{
2023-01-06 14:18:07 +01:00
var old = await _Settings . GetSettingAsync < UILNURLController . LightningAddressSettings > ( "BTCPayServer.LNURLController+LightningAddressSettings" ) ;
if ( old is not null )
{
await _Settings . UpdateSetting ( old , nameof ( UILNURLController . LightningAddressSettings ) ) ;
}
2022-01-27 17:06:02 +01:00
}
2022-04-24 05:19:34 +02:00
private async Task MigrateAddStoreToPayout ( )
{
await using var ctx = _DBContextFactory . CreateContext ( ) ;
2024-04-15 19:08:25 +09:00
await ctx . Database . ExecuteSqlRawAsync ( @ "
2022-04-24 05:19:34 +02:00
WITH cte AS (
SELECT DISTINCT p . "" Id "" , pp . "" StoreId "" FROM "" Payouts "" p
JOIN "" PullPayments "" pp ON pp . "" Id "" = p . "" PullPaymentDataId ""
WHERE p . "" StoreDataId "" IS NULL
)
UPDATE "" Payouts "" p
SET "" StoreDataId "" = cte . "" StoreId ""
FROM cte
WHERE cte . "" Id "" = p . "" Id ""
");
}
2021-11-26 16:13:41 +02:00
private async Task AddInitialUserBlob ( )
{
await using var ctx = _DBContextFactory . CreateContext ( ) ;
foreach ( var user in await ctx . Users . AsQueryable ( ) . ToArrayAsync ( ) )
{
user . SetBlob ( new UserBlob ( ) { ShowInvoiceStatusChangeHint = true } ) ;
}
await ctx . SaveChangesAsync ( ) ;
}
2021-12-31 16:59:02 +09:00
2021-10-21 17:43:02 +02:00
private async Task MigratePayoutDestinationId ( )
{
await using var ctx = _DBContextFactory . CreateContext ( ) ;
foreach ( var payoutData in await ctx . Payouts . AsQueryable ( ) . ToArrayAsync ( ) )
{
2024-05-01 10:22:07 +09:00
var pmi = payoutData . GetPayoutMethodId ( ) ;
2021-12-31 16:59:02 +09:00
if ( pmi is null )
{
continue ;
}
var handler = _payoutHandlers
2024-05-01 10:22:07 +09:00
. TryGet ( pmi ) ;
2021-12-31 16:59:02 +09:00
if ( handler is null )
{
continue ;
}
2024-05-01 10:22:07 +09:00
var claim = await handler ? . ParseClaimDestination ( payoutData . GetBlob ( _btcPayNetworkJsonSerializerSettings ) . Destination , default ) ;
2024-09-06 10:34:10 +09:00
payoutData . DedupId = claim . destination ? . Id ;
2021-10-21 17:43:02 +02:00
}
await ctx . SaveChangesAsync ( ) ;
}
2020-06-28 18:00:51 +09:00
2021-10-11 12:46:05 +02:00
private async Task MigrateAppCustomOption ( )
{
await using var ctx = _DBContextFactory . CreateContext ( ) ;
2021-10-28 12:23:21 +02:00
foreach ( var app in await ctx . Apps . Include ( data = > data . StoreData ) . AsQueryable ( ) . ToArrayAsync ( ) )
2021-10-11 12:46:05 +02:00
{
switch ( app . AppType )
{
2023-03-20 10:39:26 +09:00
case CrowdfundAppType . AppType :
2021-10-11 12:46:05 +02:00
var settings1 = app . GetSettings < CrowdfundSettings > ( ) ;
2021-10-28 12:23:21 +02:00
if ( string . IsNullOrEmpty ( settings1 . TargetCurrency ) )
{
settings1 . TargetCurrency = app . StoreData . GetStoreBlob ( ) . DefaultCurrency ;
app . SetSettings ( settings1 ) ;
}
2021-10-11 12:46:05 +02:00
break ;
2021-12-31 16:59:02 +09:00
2023-03-20 10:39:26 +09:00
case PointOfSaleAppType . AppType :
2021-12-31 16:59:02 +09:00
2022-02-26 21:19:02 -08:00
var settings2 = app . GetSettings < PointOfSaleSettings > ( ) ;
2021-10-28 12:23:21 +02:00
if ( string . IsNullOrEmpty ( settings2 . Currency ) )
{
settings2 . Currency = app . StoreData . GetStoreBlob ( ) . DefaultCurrency ;
app . SetSettings ( settings2 ) ;
}
2021-10-11 12:46:05 +02:00
break ;
}
}
await ctx . SaveChangesAsync ( ) ;
}
2021-06-17 15:36:22 +09:00
private async Task MigrateHotwalletProperty ( )
{
await using var ctx = _DBContextFactory . CreateContext ( ) ;
foreach ( var store in await ctx . Stores . AsQueryable ( ) . ToArrayAsync ( ) )
{
2024-04-04 16:31:04 +09:00
foreach ( var ( id , paymentMethod ) in store . GetPaymentMethodConfigs < DerivationSchemeSettings > ( _handlers ) )
2021-06-17 15:36:22 +09:00
{
paymentMethod . IsHotWallet = paymentMethod . Source = = "NBXplorer" ;
2021-08-10 12:13:00 +09:00
if ( paymentMethod . IsHotWallet )
{
paymentMethod . Source = "NBXplorerGenerated" ;
2024-04-04 16:31:04 +09:00
store . SetPaymentMethodConfig ( _handlers [ id ] , paymentMethod ) ;
2021-08-10 12:13:00 +09:00
}
2021-06-17 15:36:22 +09:00
}
}
await ctx . SaveChangesAsync ( ) ;
}
2021-04-28 06:14:15 +02:00
private async Task MigrateU2FToFIDO2 ( )
{
await using var ctx = _DBContextFactory . CreateContext ( ) ;
var u2fDevices = await ctx . U2FDevices . ToListAsync ( ) ;
foreach ( U2FDevice u2FDevice in u2fDevices )
{
var fido2 = new Fido2Credential ( )
{
ApplicationUserId = u2FDevice . ApplicationUserId ,
Name = u2FDevice . Name ,
Type = Fido2Credential . CredentialType . FIDO2
} ;
fido2 . SetBlob ( new Fido2CredentialBlob ( )
{
SignatureCounter = ( uint ) u2FDevice . Counter ,
2021-12-31 16:59:02 +09:00
PublicKey = CreatePublicKeyFromU2fRegistrationData ( u2FDevice . PublicKey ) . EncodeToBytes ( ) ,
2021-04-28 06:14:15 +02:00
UserHandle = u2FDevice . KeyHandle ,
Descriptor = new PublicKeyCredentialDescriptor ( u2FDevice . KeyHandle ) ,
CredType = "u2f"
} ) ;
await ctx . AddAsync ( fido2 ) ;
2021-12-31 16:59:02 +09:00
2021-04-28 06:14:15 +02:00
ctx . Remove ( u2FDevice ) ;
}
await ctx . SaveChangesAsync ( ) ;
}
//from https://github.com/abergs/fido2-net-lib/blob/0fa7bb4b4a1f33f46c5f7ca4ee489b47680d579b/Test/ExistingU2fRegistrationDataTests.cs#L70
private static CBORObject CreatePublicKeyFromU2fRegistrationData ( byte [ ] publicKeyData )
{
if ( publicKeyData . Length ! = 65 )
{
throw new ArgumentException ( "u2f public key must be 65 bytes" , nameof ( publicKeyData ) ) ;
}
var x = new byte [ 32 ] ;
var y = new byte [ 32 ] ;
Buffer . BlockCopy ( publicKeyData , 1 , x , 0 , 32 ) ;
Buffer . BlockCopy ( publicKeyData , 33 , y , 0 , 32 ) ;
var coseKey = CBORObject . NewMap ( ) ;
coseKey . Add ( COSE . KeyCommonParameter . KeyType , COSE . KeyType . EC2 ) ;
coseKey . Add ( COSE . KeyCommonParameter . Alg , - 7 ) ;
coseKey . Add ( COSE . KeyTypeParameter . Crv , COSE . EllipticCurve . P256 ) ;
coseKey . Add ( COSE . KeyTypeParameter . X , x ) ;
coseKey . Add ( COSE . KeyTypeParameter . Y , y ) ;
return coseKey ;
}
2021-03-02 11:11:58 +09:00
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 ;
2024-04-04 16:31:04 +09:00
foreach ( var prop in strats . Properties ( ) . Where ( p = > IsLN ( PaymentMethodId . Parse ( p . Name ) ) ) )
2021-03-02 11:11:58 +09:00
{
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 ) ;
}
2024-04-04 16:31:04 +09:00
internalNode . Value = new JValue ( LightningPaymentMethodConfig . InternalNode ) ;
2021-03-02 11:11:58 +09:00
}
}
if ( updated )
store . DerivationStrategies = strats . ToString ( ) ;
#pragma warning restore CS0618 // Type or member is obsolete
}
await ctx . SaveChangesAsync ( ) ;
}
2024-04-04 16:31:04 +09:00
private bool IsLN ( PaymentMethodId paymentMethodId )
{
return _handlers . TryGetValue ( paymentMethodId , out var v ) & & v is LightningLikePaymentHandler ;
}
private bool IsChain ( PaymentMethodId paymentMethodId )
{
return _handlers . TryGetValue ( paymentMethodId , out var v ) & & v is BitcoinLikePaymentHandler ;
}
private bool IsLNURL ( PaymentMethodId paymentMethodId )
{
return _handlers . TryGetValue ( paymentMethodId , out var v ) & & v is LNURLPayPaymentHandler ;
}
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 )
{
2024-09-10 17:34:02 +09:00
TimeSpan cancellationTimeout = TimeSpan . FromDays ( 1.0 ) ;
using ( CancellationTokenSource timeout = new CancellationTokenSource ( ( int ) cancellationTimeout . TotalMilliseconds ) )
2020-06-28 18:00:51 +09:00
using ( CancellationTokenSource cts = CancellationTokenSource . CreateLinkedTokenSource ( timeout . Token , cancellationToken ) )
{
retry :
try
{
2024-04-25 17:27:45 +09:00
_logger . LogInformation ( "Running the migration scripts..." ) ;
2024-09-10 17:34:02 +09:00
var db = _DBContextFactory . CreateContext ( o = > o . CommandTimeout ( ( ( int ) cancellationTimeout . TotalSeconds ) + 1 ) ) ;
2024-04-25 17:27:45 +09:00
await db . Database . MigrateAsync ( timeout . Token ) ;
_logger . LogInformation ( "All migration scripts ran successfully" ) ;
2020-06-28 18:00:51 +09:00
}
// Starting up
2023-02-10 11:43:46 +09:00
catch ( ConfigException ) { throw ; }
2024-04-25 17:27:45 +09:00
catch ( Exception ex ) when ( ! cts . Token . IsCancellationRequested )
2020-06-28 18:00:51 +09:00
{
try
{
2024-04-25 17:27:45 +09:00
_logger . LogWarning ( ex , "Error while running migration scripts, retrying..." ) ;
2020-06-28 18:00:51 +09:00
await Task . Delay ( 1000 , cts . Token ) ;
}
catch { }
goto retry ;
}
}
}
2024-04-04 16:31:04 +09:00
void MigrateDerivationSettings ( DerivationSchemeSettings s , BTCPayNetwork network )
{
if ( network = = null | | s . AccountKeySettings is not ( null or { Length : 1 } ) )
return ;
s . AccountKeySettings = s . AccountDerivation . GetExtPubKeys ( ) . Select ( e = > new AccountKeySettings ( )
{
AccountKey = e . GetWif ( network . NBitcoinNetwork ) ,
} ) . ToArray ( ) ;
#pragma warning disable CS0618 // Type or member is obsolete
s . AccountKeySettings [ 0 ] . AccountKeyPath = s . AccountKeyPath ;
s . AccountKeySettings [ 0 ] . RootFingerprint = s . RootFingerprint ;
s . ExplicitAccountKey = null ;
s . AccountKeyPath = null ;
s . RootFingerprint = null ;
#pragma warning restore CS0618 // Type or member is obsolete
}
2020-06-28 18:00:51 +09:00
2020-12-29 09:58:35 +01:00
private async Task MigratePaymentMethodCriteria ( )
{
2022-01-14 17:50:29 +09:00
using var ctx = _DBContextFactory . CreateContext ( ) ;
foreach ( var store in await ctx . Stores . AsQueryable ( ) . ToArrayAsync ( ) )
2020-12-29 09:58:35 +01:00
{
2022-01-14 17:50:29 +09:00
var blob = store . GetStoreBlob ( ) ;
2020-12-29 09:58:35 +01:00
2022-01-14 17:50:29 +09:00
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" ) ;
}
2024-04-04 16:31:04 +09:00
blob . PaymentMethodCriteria = store . GetPaymentMethodConfigs ( ) . Select ( c = > c . Key ) . Select ( paymentMethodId = >
2022-01-14 17:50:29 +09:00
{
var matchedFromBlob =
blob . PaymentMethodCriteria ? . SingleOrDefault ( criteria = > criteria . PaymentMethod = = paymentMethodId & & criteria . Value ! = null ) ;
return matchedFromBlob switch
2021-12-31 16:59:02 +09:00
{
2024-04-04 16:31:04 +09:00
null when _handlers . TryGet ( paymentMethodId ) is LightningLikePaymentHandler & &
2022-01-14 17:50:29 +09:00
lightningMaxValue ! = null = > new PaymentMethodCriteria ( )
{
Above = false ,
PaymentMethod = paymentMethodId ,
Value = lightningMaxValue
} ,
2024-04-04 16:31:04 +09:00
null when _handlers . TryGet ( paymentMethodId ) is BitcoinLikePaymentHandler & &
2022-01-14 17:50:29 +09:00
onChainMinValue ! = null = > new PaymentMethodCriteria ( )
{
Above = true ,
PaymentMethod = paymentMethodId ,
Value = onChainMinValue
} ,
_ = > new PaymentMethodCriteria ( )
2021-12-31 16:59:02 +09:00
{
2022-01-14 17:50:29 +09:00
PaymentMethod = paymentMethodId ,
Above = matchedFromBlob ? . Above ? ? true ,
Value = matchedFromBlob ? . Value
}
} ;
} ) . ToList ( ) ;
2020-12-29 09:58:35 +01:00
2022-01-14 17:50:29 +09:00
store . SetStoreBlob ( blob ) ;
2020-12-29 09:58:35 +01:00
}
2022-01-14 17:50:29 +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
}
}