2020-06-28 21:44:35 -05:00
using System ;
2020-12-28 11:10:53 +01:00
using System.Collections.Generic ;
2022-04-24 05:19:34 +02:00
using System.Diagnostics ;
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-06-28 18:00:51 +09:00
using BTCPayServer.Client.Models ;
2020-12-28 11:10:53 +01:00
using BTCPayServer.Configuration ;
2021-10-11 12:46:05 +02:00
using BTCPayServer.Controllers ;
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-06-28 18:00:51 +09:00
using BTCPayServer.Logging ;
2021-10-11 12:46:05 +02:00
using BTCPayServer.Models.AppViewModels ;
2020-12-29 09:58:35 +01:00
using BTCPayServer.Payments ;
2021-03-02 11:11:58 +09:00
using BTCPayServer.Payments.Lightning ;
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 ;
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-10-11 12:46:05 +02:00
using ExchangeSharp ;
2021-04-28 06:14:15 +02:00
using Fido2NetLib.Objects ;
2020-06-28 18:00:51 +09:00
using Microsoft.AspNetCore.Identity ;
using Microsoft.EntityFrameworkCore ;
using Microsoft.Extensions.Logging ;
2021-03-02 11:11:58 +09:00
using Microsoft.Extensions.Options ;
2022-04-24 05:19:34 +02:00
using NBitcoin ;
using NBitcoin.DataEncoders ;
2020-12-29 09:58:35 +01:00
using NBXplorer ;
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-01-23 10:11:34 +01:00
using LightningAddressData = BTCPayServer . Data . LightningAddressData ;
2022-04-24 05:19:34 +02:00
using PayoutData = BTCPayServer . Data . PayoutData ;
using PullPaymentData = BTCPayServer . Data . PullPaymentData ;
using StoreData = BTCPayServer . Data . StoreData ;
2020-06-28 18:00:51 +09:00
namespace BTCPayServer.Hosting
{
public class MigrationStartupTask : IStartupTask
{
2021-11-22 17:16:08 +09:00
public Logs Logs { get ; }
2020-06-28 22:07:48 -05:00
private readonly ApplicationDbContextFactory _DBContextFactory ;
private readonly StoreRepository _StoreRepository ;
private readonly BTCPayNetworkProvider _NetworkProvider ;
private readonly SettingsRepository _Settings ;
2021-10-11 12:46:05 +02:00
private readonly AppService _appService ;
2021-10-21 17:43:02 +02:00
private readonly IEnumerable < IPayoutHandler > _payoutHandlers ;
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 ;
2020-06-28 18:00:51 +09:00
private readonly UserManager < ApplicationUser > _userManager ;
2021-03-02 11:11:58 +09:00
public IOptions < LightningNetworkOptions > LightningOptions { get ; }
2020-06-28 18:00:51 +09:00
public MigrationStartupTask (
BTCPayNetworkProvider networkProvider ,
StoreRepository storeRepository ,
ApplicationDbContextFactory dbContextFactory ,
UserManager < ApplicationUser > userManager ,
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 ,
2021-10-21 17:43:02 +02:00
IEnumerable < IPayoutHandler > payoutHandlers ,
2021-11-22 17:16:08 +09:00
BTCPayNetworkJsonSerializerSettings btcPayNetworkJsonSerializerSettings ,
2022-04-19 09:58:31 +02:00
LightningAddressService lightningAddressService ,
2022-04-24 05:19:34 +02:00
ILogger < MigrationStartupTask > logger )
2020-06-28 18:00:51 +09:00
{
_DBContextFactory = dbContextFactory ;
_StoreRepository = storeRepository ;
_NetworkProvider = networkProvider ;
_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 ;
2020-06-28 18:00:51 +09:00
_userManager = userManager ;
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
2022-06-29 23:38:44 +09:00
settings . CheckedFirstRun = false ;
2022-12-12 12:28:24 +01:00
settings . FileSystemStorageAsDefault = false ;
2022-06-29 23:38:44 +09:00
await _Settings . UpdateSetting ( settings ) ;
}
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 ) ;
}
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-03-07 10:26:47 +09:00
if ( ! settings . FixSeqAfterSqliteMigration )
{
await FixSeqAfterSqliteMigration ( ) ;
settings . FixSeqAfterSqliteMigration = 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
2023-03-07 10:26:47 +09:00
private async Task FixSeqAfterSqliteMigration ( )
{
await using var ctx = _DBContextFactory . CreateContext ( ) ;
if ( ! ctx . Database . IsNpgsql ( ) )
return ;
var state = await ToPostgresMigrationStartupTask . GetMigrationState ( ctx ) ;
if ( state ! = "complete" )
return ;
await ToPostgresMigrationStartupTask . UpdateSequenceInvoiceSearch ( ctx ) ;
}
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 ) ;
var labelObjId = new WalletObjectId ( WalletId . Parse ( wallet . Id ) ,
WalletObjectData . Types . Label ,
labelId ) ;
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 ( ) ;
if ( ctx . Database . IsNpgsql ( ) )
{
await ctx . Database . ExecuteSqlRawAsync ( @ "
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 ""
");
}
else
{
var queryable = ctx . Payouts . Where ( data = > data . StoreDataId = = null ) ;
var count = await queryable . CountAsync ( ) ;
_logger . LogInformation ( $"Migrating {count} payouts to have a store id explicitly" ) ;
2023-01-06 14:18:07 +01:00
for ( int i = 0 ; i < count ; i + = 1000 )
2022-04-24 05:19:34 +02:00
{
await queryable . Include ( data = > data . PullPaymentData ) . Skip ( i ) . Take ( 1000 )
. ForEachAsync ( data = > data . StoreDataId = data . PullPaymentData . StoreId ) ;
2023-01-06 14:18:07 +01:00
2022-04-24 05:19:34 +02:00
await ctx . SaveChangesAsync ( ) ;
2023-01-06 14:18:07 +01:00
_logger . LogInformation ( $"Migrated {i + 1000}/{count} payouts to have a store id explicitly" ) ;
2022-04-24 05:19:34 +02:00
}
}
}
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 ( ) )
{
2021-12-31 16:59:02 +09:00
var pmi = payoutData . GetPaymentMethodId ( ) ;
if ( pmi is null )
{
continue ;
}
var handler = _payoutHandlers
. FindPayoutHandler ( pmi ) ;
if ( handler is null )
{
continue ;
}
2022-11-22 20:17:29 +09:00
var claim = await handler ? . ParseClaimDestination ( pmi , payoutData . GetBlob ( _btcPayNetworkJsonSerializerSettings ) . Destination , default ) ;
2021-12-31 16:59:02 +09:00
payoutData . Destination = 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
{
ViewPointOfSaleViewModel . Item [ ] items ;
string newTemplate ;
switch ( app . AppType )
{
case nameof ( AppType . Crowdfund ) :
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
items = _appService . Parse ( settings1 . PerksTemplate , settings1 . TargetCurrency ) ;
newTemplate = _appService . SerializeTemplate ( items ) ;
if ( settings1 . PerksTemplate ! = newTemplate )
{
settings1 . PerksTemplate = newTemplate ;
app . SetSettings ( settings1 ) ;
} ;
break ;
2021-12-31 16:59:02 +09:00
2021-10-11 12:46:05 +02:00
case nameof ( AppType . PointOfSale ) :
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
items = _appService . Parse ( settings2 . Template , settings2 . Currency ) ;
newTemplate = _appService . SerializeTemplate ( items ) ;
if ( settings2 . Template ! = newTemplate )
{
settings2 . Template = newTemplate ;
app . SetSettings ( settings2 ) ;
} ;
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 ( ) )
{
foreach ( var paymentMethod in store . GetSupportedPaymentMethods ( _NetworkProvider ) . OfType < DerivationSchemeSettings > ( ) )
{
paymentMethod . IsHotWallet = paymentMethod . Source = = "NBXplorer" ;
2021-08-10 12:13:00 +09:00
if ( paymentMethod . IsHotWallet )
{
paymentMethod . Source = "NBXplorerGenerated" ;
store . SetSupportedPaymentMethod ( paymentMethod ) ;
}
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 ;
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 ( ) ;
}
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
{
2023-02-10 11:43:46 +09:00
var db = _DBContextFactory . CreateContext ( ) ;
await db . Database . MigrateAsync ( ) ;
if ( db . Database . IsNpgsql ( ) )
{
if ( await db . GetMigrationState ( ) = = "pending" )
throw new ConfigException ( "This database hasn't been completely migrated, please retry migration by setting the BTCPAY_SQLITEFILE or BTCPAY_MYSQL setting on top of BTCPAY_POSTGRES" ) ;
}
2020-06-28 18:00:51 +09:00
}
// Starting up
2023-02-10 11:43:46 +09:00
catch ( ConfigException ) { throw ; }
2020-06-28 18:00:51 +09:00
catch when ( ! cts . Token . IsCancellationRequested )
{
try
{
await Task . Delay ( 1000 , cts . Token ) ;
}
catch { }
goto retry ;
}
}
}
private async Task ConvertConvertWalletKeyPathRoots ( )
{
bool save = false ;
2022-01-14 17:50:29 +09:00
using var ctx = _DBContextFactory . CreateContext ( ) ;
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
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
if ( blob . AdditionalData . TryGetValue ( "walletKeyPathRoots" , out var walletKeyPathRootsJToken ) )
{
var walletKeyPathRoots = walletKeyPathRootsJToken . ToObject < Dictionary < string , string > > ( ) ;
2020-12-29 09:58:35 +01:00
2022-01-14 17:50:29 +09:00
if ( ! ( walletKeyPathRoots ? . Any ( ) is true ) )
continue ;
foreach ( var scheme in store . GetSupportedPaymentMethods ( _NetworkProvider )
. OfType < DerivationSchemeSettings > ( ) )
{
if ( walletKeyPathRoots . TryGetValue ( scheme . PaymentId . ToString ( ) . ToLowerInvariant ( ) ,
out var root ) )
2020-06-28 18:00:51 +09:00
{
2022-01-14 17:50:29 +09:00
scheme . AccountKeyPath = new NBitcoin . KeyPath ( root ) ;
store . SetSupportedPaymentMethod ( scheme ) ;
save = true ;
2020-06-28 18:00:51 +09:00
}
}
2022-01-14 17:50:29 +09:00
blob . AdditionalData . Remove ( "walletKeyPathRoots" ) ;
store . SetStoreBlob ( blob ) ;
2020-06-28 18:00:51 +09:00
}
2022-01-14 17:50:29 +09:00
#pragma warning restore CS0618 // Type or member is obsolete
2020-06-28 18:00:51 +09:00
}
2022-01-14 17:50:29 +09:00
if ( save )
await ctx . SaveChangesAsync ( ) ;
2020-06-28 18:00:51 +09:00
}
private async Task ConvertCrowdfundOldSettings ( )
{
2022-01-14 17:50:29 +09:00
using var ctx = _DBContextFactory . CreateContext ( ) ;
foreach ( var app in await ctx . Apps . Where ( a = > a . AppType = = "Crowdfund" ) . ToArrayAsync ( ) )
2020-06-28 18:00:51 +09:00
{
2022-01-14 17:50:29 +09:00
var settings = app . GetSettings < Services . Apps . CrowdfundSettings > ( ) ;
2020-06-28 18:00:51 +09:00
#pragma warning disable CS0618 // Type or member is obsolete
2022-01-14 17:50:29 +09:00
if ( settings . UseAllStoreInvoices )
2020-06-28 18:00:51 +09:00
#pragma warning restore CS0618 // Type or member is obsolete
2022-01-14 17:50:29 +09:00
{
app . TagAllInvoices = true ;
2020-06-28 18:00:51 +09:00
}
}
2022-01-14 17:50:29 +09:00
await ctx . SaveChangesAsync ( ) ;
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" ) ;
}
blob . PaymentMethodCriteria = store . GetEnabledPaymentIds ( _NetworkProvider ) . Select ( paymentMethodId = >
{
var matchedFromBlob =
blob . PaymentMethodCriteria ? . SingleOrDefault ( criteria = > criteria . PaymentMethod = = paymentMethodId & & criteria . Value ! = null ) ;
return matchedFromBlob switch
2021-12-31 16:59:02 +09:00
{
2022-01-14 17:50:29 +09:00
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 ( )
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
}
2020-06-28 18:00:51 +09:00
private async Task ConvertNetworkFeeProperty ( )
{
2022-01-14 17:50:29 +09:00
using var ctx = _DBContextFactory . CreateContext ( ) ;
foreach ( var store in await ctx . Stores . AsQueryable ( ) . ToArrayAsync ( ) )
2020-06-28 18:00:51 +09:00
{
2022-01-14 17:50:29 +09:00
var blob = store . GetStoreBlob ( ) ;
if ( blob . AdditionalData . TryGetValue ( "networkFeeDisabled" , out var networkFeeModeJToken ) )
2020-06-28 18:00:51 +09:00
{
2022-01-14 17:50:29 +09:00
var networkFeeMode = networkFeeModeJToken . ToObject < bool? > ( ) ;
if ( networkFeeMode ! = null )
2020-06-28 18:00:51 +09:00
{
2022-01-14 17:50:29 +09:00
blob . NetworkFeeMode = networkFeeMode . Value ? NetworkFeeMode . Never : NetworkFeeMode . Always ;
2020-06-28 18:00:51 +09:00
}
2022-01-14 17:50:29 +09:00
blob . AdditionalData . Remove ( "networkFeeDisabled" ) ;
store . SetStoreBlob ( blob ) ;
2020-06-28 18:00:51 +09:00
}
}
2022-01-14 17:50:29 +09:00
await ctx . SaveChangesAsync ( ) ;
2020-06-28 18:00:51 +09:00
}
private async Task ConvertMultiplierToSpread ( )
{
2022-01-14 17:50:29 +09:00
using var ctx = _DBContextFactory . CreateContext ( ) ;
foreach ( var store in await ctx . Stores . AsQueryable ( ) . ToArrayAsync ( ) )
2020-06-28 18:00:51 +09:00
{
2022-01-14 17:50:29 +09:00
var blob = store . GetStoreBlob ( ) ;
decimal multiplier = 1.0 m ;
if ( blob . AdditionalData . TryGetValue ( "rateRules" , out var rateRulesJToken ) )
2020-06-28 18:00:51 +09:00
{
2022-01-14 17:50:29 +09: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
{
2022-01-14 17:50:29 +09:00
foreach ( var rule in rateRules )
2020-06-28 18:00:51 +09:00
{
2022-01-14 17:50:29 +09:00
multiplier = rule . Apply ( null , multiplier ) ;
2020-06-28 18:00:51 +09:00
}
}
2022-01-14 17:50:29 +09:00
blob . AdditionalData . Remove ( "rateRules" ) ;
blob . Spread = Math . Min ( 1.0 m , Math . Max ( 0 m , - ( multiplier - 1.0 m ) ) ) ;
store . SetStoreBlob ( blob ) ;
2020-06-28 18:00:51 +09:00
}
}
2022-01-14 17:50:29 +09:00
await ctx . SaveChangesAsync ( ) ;
2020-06-28 18:00:51 +09:00
}
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 ( )
{
2022-01-14 17:50:29 +09:00
using var ctx = _DBContextFactory . CreateContext ( ) ;
foreach ( var store in await ctx . Stores . AsQueryable ( ) . ToArrayAsync ( ) )
2020-06-28 18:00:51 +09:00
{
2022-01-14 17:50:29 +09:00
foreach ( var method in store . GetSupportedPaymentMethods ( _NetworkProvider ) . OfType < Payments . Lightning . LightningSupportedPaymentMethod > ( ) )
2020-06-28 18:00:51 +09:00
{
2022-01-14 17:50:29 +09:00
var lightning = method . GetExternalLightningUrl ( ) ;
if ( lightning ? . IsLegacy is true )
2020-06-28 18:00:51 +09:00
{
2022-01-14 17:50:29 +09:00
method . SetLightningUrl ( lightning ) ;
store . SetSupportedPaymentMethod ( method ) ;
2020-06-28 18:00:51 +09:00
}
}
}
2022-01-14 17:50:29 +09:00
await ctx . SaveChangesAsync ( ) ;
2020-06-28 18:00:51 +09:00
}
}
}