This commit is contained in:
Kukks 2024-07-25 14:50:25 +02:00
parent ec215938df
commit 9c09a440e2
No known key found for this signature in database
GPG Key ID: 8E5530D9D1C93097
28 changed files with 484 additions and 235 deletions

View File

@ -42,14 +42,14 @@ public class LDKKVStore:KVStoreInterface
public Result_NoneIOErrorZ write(string primary_namespace, string secondary_namespace, string key, byte[] buf)
{
var key1 = CombineKey(primary_namespace, secondary_namespace, key);
_configProvider.Set(key1, buf).ConfigureAwait(false).GetAwaiter().GetResult();
_configProvider.Set(key1, buf, true).ConfigureAwait(false).GetAwaiter().GetResult();
return Result_NoneIOErrorZ.ok();
}
public Result_NoneIOErrorZ remove(string primary_namespace, string secondary_namespace, string key, bool lazy)
{
var key1 = CombineKey(primary_namespace, secondary_namespace, key);
_configProvider.Set<byte[]>(key1, null).ConfigureAwait(false).GetAwaiter().GetResult();
_configProvider.Set<byte[]>(key1, null, true).ConfigureAwait(false).GetAwaiter().GetResult();
return Result_NoneIOErrorZ.ok();
}

View File

@ -202,7 +202,7 @@ public partial class LDKNode : IAsyncDisposable, IHostedService, IDisposable
public async Task UpdateConfig(LightningConfig config)
{
await _started.Task;
await _configProvider.Set(LightningConfig.Key, config);
await _configProvider.Set(LightningConfig.Key, config, true);
_config = config;
ConfigUpdated?.Invoke(this, config);
@ -296,18 +296,18 @@ public partial class LDKNode : IAsyncDisposable, IHostedService, IDisposable
public async Task UpdateChannelManager(ChannelManager serializedChannelManager)
{
await _configProvider.Set("ln:ChannelManager", serializedChannelManager.write());
await _configProvider.Set("ln:ChannelManager", serializedChannelManager.write(), true);
}
public async Task UpdateNetworkGraph(NetworkGraph networkGraph)
{
await _configProvider.Set("ln:NetworkGraph", networkGraph.write());
await _configProvider.Set("ln:NetworkGraph", networkGraph.write(), true);
}
public async Task UpdateScore(WriteableScore score)
{
await _configProvider.Set("ln:Score", score.write());
await _configProvider.Set("ln:Score", score.write(), true);
}

View File

@ -134,7 +134,7 @@ public class OnChainWalletManager : BaseHostedService
walletConfig.Derivations[keyValuePair.Key].Identifier = keyValuePair.Value;
}
await _configProvider.Set(WalletConfig.Key, walletConfig);
await _configProvider.Set(WalletConfig.Key, walletConfig, true);
WalletConfig = walletConfig;
State = OnChainWalletState.Loaded;
}
@ -169,7 +169,7 @@ public class OnChainWalletManager : BaseHostedService
Descriptor = descriptor,
Identifier = result[key]
};
await _configProvider.Set(WalletConfig.Key, WalletConfig);
await _configProvider.Set(WalletConfig.Key, WalletConfig, true);
}
finally
{
@ -436,7 +436,7 @@ public class OnChainWalletManager : BaseHostedService
var updated = key.Aggregate(false, (current, k) => current || WalletConfig.Derivations.Remove(k));
if (updated)
await _configProvider.Set(WalletConfig.Key, WalletConfig);
await _configProvider.Set(WalletConfig.Key, WalletConfig, true);
}
finally
{

View File

@ -409,12 +409,12 @@ public class AuthStateProvider : AuthenticationStateProvider, IAccountManager, I
public async Task UpdateAccount(BTCPayAccount account)
{
await _config.Set(GetKey(account.Id), account);
await _config.Set(GetKey(account.Id), account, true);
}
public async Task RemoveAccount(BTCPayAccount account)
{
await _config.Set<BTCPayAccount>(GetKey(account.Id), null);
await _config.Set<BTCPayAccount>(GetKey(account.Id), null, true);
}
private async Task<BTCPayAccount> GetAccount(string serverUrl, string email)
@ -435,7 +435,7 @@ public class AuthStateProvider : AuthenticationStateProvider, IAccountManager, I
{
OnBeforeAccountChange?.Invoke(this, _account);
if (account != null) await UpdateAccount(account);
await _config.Set(CurrentAccountKey, account?.Id);
await _config.Set(CurrentAccountKey, account?.Id, true);
_account = account;
_userInfo = null;

View File

@ -14,6 +14,8 @@
<PackageReference Include="AsyncKeyedLock" Version="7.0.0" />
<PackageReference Include="FlexLabs.EntityFrameworkCore.Upsert" Version="8.0.0" />
<PackageReference Include="Laraue.EfCoreTriggers.SqlLite" Version="8.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="8.0.6" />

View File

@ -3,6 +3,6 @@
public interface IConfigProvider
{
Task<T?> Get<T>(string key);
Task Set<T>(string key, T? value);
Task Set<T>(string key, T? value, bool backup);
Task<IEnumerable<string>> List(string prefix);
}

View File

@ -1,3 +1,8 @@
namespace BTCPayApp.Core.Contracts;
public interface ISecureConfigProvider : IConfigProvider;
public interface ISecureConfigProvider
{
Task<T?> Get<T>(string key);
Task Set<T>(string key, T? value);
}

View File

@ -30,7 +30,7 @@ public class AppDbContext : DbContext
modelBuilder.Entity<Outbox>()
.HasKey(w => new {w.Entity, w.Key, w.ActionType, w.Version});
modelBuilder.Entity<Outbox>().Property(payment => payment.Timestamp).HasDefaultValueSql("datetime('now')");
modelBuilder.Entity<AppLightningPayment>().Property(payment => payment.PaymentRequest)
.HasConversion(
request => request.ToString(),
@ -57,12 +57,12 @@ public class AppDbContext : DbContext
//handling versioned data
//settings, channels, payments
//when creating, set the version to 0
//when updating, increment the version
// outbox creation
// when creating, insert an outbox item
// when updating, insert an outbox item
@ -94,110 +94,110 @@ public class AppDbContext : DbContext
ActionType = OutboxAction.Delete
})))
.AfterUpdate(trigger => trigger
.Action(group => group.Update<Setting>(
(tableRefs, setting) => tableRefs.Old.Key == setting.Key,
(tableRefs, setting) => new Setting() {Version = tableRefs.Old.Version + 1})));
// .AfterUpdate(trigger => trigger
.Action(group => group
// .Condition(@ref => @ref.Old.Value != @ref.New.Value)
.Update<Setting>(
(tableRefs, setting) => tableRefs.Old.Key == setting.Key,
(tableRefs, setting) => new Setting() {Version = tableRefs.Old.Version + 1})
.Insert(
// .InsertIfNotExists( (@ref, outbox) => @ref.New.Version == outbox.Version && outbox.ActionType == OutboxAction.Update && outbox.Entity == "Setting" && outbox.Key == @ref.New.Key,
@ref => new Outbox()
{
Entity = "Setting",
Version = @ref.Old.Version + 1,
Key = @ref.New.Key,
ActionType = OutboxAction.Update
})));
// .Action(group => group
// .Condition(@ref => @ref.Old.Backup)
// .Condition(@ref => @ref.Old.Backup && !@ref.New.Backup)
// .Insert(
// // .InsertIfNotExists( (@ref, outbox) => @ref.New.Version == outbox.Version && outbox.ActionType == OutboxAction.Update && outbox.Entity == "Setting" && outbox.Key == @ref.New.Key,
// // .InsertIfNotExists( (@ref, outbox) => @ref.New.Version == outbox.Version && outbox.ActionType == OutboxAction.Update && outbox.Entity == "Setting" && outbox.Key == @ref.New.Key,
// @ref => new Outbox()
// {
// Entity = "Setting",
// Version = @ref.New.Version,
// Version = @ref.Old.Version +1,
// Key = @ref.New.Key,
// ActionType = OutboxAction.Update
// ActionType = OutboxAction.Delete
// })));
modelBuilder.Entity<Channel>()
.AfterInsert(trigger => trigger
.Action(group => group
.Insert(
// .InsertIfNotExists( (@ref, outbox) => outbox.Version == @ref.New.Version && outbox.ActionType == OutboxAction.Insert && outbox.Entity == "Channel" && outbox.Key == @ref.New.Id,
@ref => new Outbox()
{
Entity = "Channel",
Version = @ref.New.Version,
Key = @ref.New.Id,
ActionType = OutboxAction.Insert
})))
.AfterDelete(trigger => trigger
.Action(group => group
.Insert(
// .InsertIfNotExists( (@ref, outbox) => @ref.Old.Version == outbox.Version && outbox.ActionType == OutboxAction.Delete && outbox.Entity == "Channel" && outbox.Key == @ref.Old.Id,
@ref => new Outbox()
{
Entity = "Channel",
Version = @ref.Old.Version,
Key = @ref.Old.Id,
ActionType = OutboxAction.Delete
})))
modelBuilder.Entity<Channel>()
.AfterInsert(trigger => trigger
.Action(group => group
.Insert(
// .InsertIfNotExists( (@ref, outbox) => outbox.Version == @ref.New.Version && outbox.ActionType == OutboxAction.Insert && outbox.Entity == "Channel" && outbox.Key == @ref.New.Id,
@ref => new Outbox()
{
Entity = "Channel",
Version = @ref.New.Version,
Key = @ref.New.Id,
ActionType = OutboxAction.Insert
})))
.AfterDelete(trigger => trigger
.Action(group => group
.Insert(
// .InsertIfNotExists( (@ref, outbox) => @ref.Old.Version == outbox.Version && outbox.ActionType == OutboxAction.Delete && outbox.Entity == "Channel" && outbox.Key == @ref.Old.Id,
@ref => new Outbox()
{
Entity = "Channel",
Version = @ref.Old.Version,
Key = @ref.Old.Id,
ActionType = OutboxAction.Delete
})))
.AfterUpdate(trigger => trigger
.Action(group => group.Update<Channel>(
(tableRefs, setting) => tableRefs.Old.Id == setting.Id,
(tableRefs, setting) => new Channel() {Version = tableRefs.Old.Version + 1}).Insert(
// .InsertIfNotExists( (@ref, outbox) => @ref.New.Version == outbox.Version && outbox.ActionType == OutboxAction.Update && outbox.Entity == "Channel" && outbox.Key == @ref.New.Id,
@ref => new Outbox()
{
Entity = "Channel",
Version = @ref.Old.Version +1,
Key = @ref.New.Id,
ActionType = OutboxAction.Update
})));
.AfterUpdate(trigger => trigger
.Action(group => group.Update<Channel>(
(tableRefs, setting) => tableRefs.Old.Id == setting.Id,
(tableRefs, setting) => new Channel() {Version = tableRefs.Old.Version + 1})))
;
// .AfterUpdate(trigger => trigger
// .Action(group => group
// .Insert(
// // .InsertIfNotExists( (@ref, outbox) => @ref.New.Version == outbox.Version && outbox.ActionType == OutboxAction.Update && outbox.Entity == "Channel" && outbox.Key == @ref.New.Id,
// @ref => new Outbox()
// {
// Entity = "Channel",
// Version = @ref.New.Version,
// Key = @ref.New.Id,
// ActionType = OutboxAction.Update
// })));
//
modelBuilder.Entity<AppLightningPayment>()
.AfterInsert(trigger => trigger
.Action(group => group
.Insert(
// .InsertIfNotExists( (@ref, outbox) => outbox.Version == @ref.New.Version && outbox.ActionType == OutboxAction.Insert && outbox.Entity == "Payment" && outbox.Key == @ref.New.PaymentHash+ "_"+@ref.New.PaymentId+ "_"+@ref.New.Inbound,
@ref => new Outbox()
{
Entity = "Payment",
Version = @ref.New.Version,
Key = @ref.New.PaymentHash + "_" + @ref.New.PaymentId + "_" + @ref.New.Inbound,
ActionType = OutboxAction.Insert
})))
.AfterDelete(trigger => trigger
.Action(group => group
.Insert(
// .InsertIfNotExists( (@ref, outbox) => @ref.Old.Version == outbox.Version && outbox.ActionType == OutboxAction.Delete && outbox.Entity == "Payment" && outbox.Key == @ref.Old.PaymentHash+ "_"+@ref.Old.PaymentId+ "_"+@ref.Old.Inbound,
@ref => new Outbox()
{
Entity = "Payment",
Version = @ref.Old.Version,
Key = @ref.Old.PaymentHash + "_" + @ref.Old.PaymentId + "_" + @ref.Old.Inbound,
ActionType = OutboxAction.Delete
})))
.AfterUpdate(trigger => trigger
.Action(group => group.Update<AppLightningPayment>(
(tableRefs, setting) => tableRefs.Old.PaymentHash == setting.PaymentHash,
(tableRefs, setting) => new AppLightningPayment() {Version = tableRefs.Old.Version + 1})))
;
// .AfterUpdate(trigger => trigger
// .Action(group => group
// .Insert(
// // .InsertIfNotExists( (@ref, outbox) =>
// // outbox.Version != @ref.New.Version || outbox.ActionType != OutboxAction.Update || outbox.Entity != "Payment" || outbox.Key != @ref.New.PaymentHash+ "_"+@ref.New.PaymentId+ "_"+@ref.New.Inbound,
// @ref => new Outbox()
// {
// Entity = "Payment",
// Version = @ref.New.Version,
// Key = @ref.New.PaymentHash+ "_"+@ref.New.PaymentId+ "_"+@ref.New.Inbound,
// ActionType = OutboxAction.Update
// })));
modelBuilder.Entity<AppLightningPayment>()
.AfterInsert(trigger => trigger
.Action(group => group
.Insert(
// .InsertIfNotExists( (@ref, outbox) => outbox.Version == @ref.New.Version && outbox.ActionType == OutboxAction.Insert && outbox.Entity == "Payment" && outbox.Key == @ref.New.PaymentHash+ "_"+@ref.New.PaymentId+ "_"+@ref.New.Inbound,
@ref => new Outbox()
{
Entity = "Payment",
Version = @ref.New.Version,
Key = @ref.New.PaymentHash + "_" + @ref.New.PaymentId + "_" + @ref.New.Inbound,
ActionType = OutboxAction.Insert
})))
.AfterDelete(trigger => trigger
.Action(group => group
.Insert(
// .InsertIfNotExists( (@ref, outbox) => @ref.Old.Version == outbox.Version && outbox.ActionType == OutboxAction.Delete && outbox.Entity == "Payment" && outbox.Key == @ref.Old.PaymentHash+ "_"+@ref.Old.PaymentId+ "_"+@ref.Old.Inbound,
@ref => new Outbox()
{
Entity = "Payment",
Version = @ref.Old.Version,
Key = @ref.Old.PaymentHash + "_" + @ref.Old.PaymentId + "_" + @ref.Old.Inbound,
ActionType = OutboxAction.Delete
})))
.AfterUpdate(trigger => trigger
.Action(group =>
group.Update<AppLightningPayment>(
(tableRefs, setting) => tableRefs.Old.PaymentHash == setting.PaymentHash,
(tableRefs, setting) => new AppLightningPayment() {Version = tableRefs.Old.Version + 1}).Insert(
// .InsertIfNotExists( (@ref, outbox) =>
// outbox.Version != @ref.New.Version || outbox.ActionType != OutboxAction.Update || outbox.Entity != "Payment" || outbox.Key != @ref.New.PaymentHash+ "_"+@ref.New.PaymentId+ "_"+@ref.New.Inbound,
@ref => new Outbox()
{
Entity = "Payment",
Version = @ref.Old.Version +1,
Key = @ref.New.PaymentHash + "_" + @ref.New.PaymentId + "_" + @ref.New.Inbound,
ActionType = OutboxAction.Update
})));
base.OnModelCreating(modelBuilder);
}
}
public enum OutboxAction
{
Insert,
@ -211,36 +211,5 @@ public class Outbox
public OutboxAction ActionType { get; set; }
public string Key { get; set; }
public string Entity { get; set; }
public ulong Version { get; set; }
}
public class OutboxProcessor : IHostedService
{
private readonly IDbContextFactory<AppDbContext> _dbContextFactory;
public OutboxProcessor(IDbContextFactory<AppDbContext> dbContextFactory)
{
_dbContextFactory = dbContextFactory;
}
private async Task ProcessOutbox(CancellationToken cancellationToken = default)
{
await using var db =
new AppDbContext(new DbContextOptionsBuilder<AppDbContext>().UseSqlite("Data Source=outbox.db").Options);
var outbox = db.Set<Outbox>();
var outboxItems = await outbox.ToListAsync();
foreach (var outboxItem in outboxItems)
{
// Process outbox item
}
}
public async Task StartAsync(CancellationToken cancellationToken)
{
}
public async Task StopAsync(CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public long Version { get; set; }
}

View File

@ -6,7 +6,7 @@ using NBitcoin;
namespace BTCPayApp.Core.Data;
public class AppLightningPayment : VersionedData
public class AppLightningPayment : VersionedData<AppLightningPayment>
{
[JsonConverter(typeof(UInt256JsonConverter))]
public uint256 PaymentHash { get; set; }

View File

@ -1,4 +1,6 @@
namespace BTCPayApp.Core.Data;
using System.Text.Json.Serialization;
namespace BTCPayApp.Core.Data;
public class Channel:VersionedData
{
@ -12,5 +14,8 @@ public class ChannelAlias
public string Id { get; set; }
public string Type { get; set; }
public string ChannelId { get; set; }
[JsonIgnore]
public Channel Channel { get; set; }
}

View File

@ -5,18 +5,19 @@ namespace BTCPayApp.Core.Data;
public static class EFExtensions
{
public static async Task<int> CrappyUpsert<T>(this DbContext ctx, T item, CancellationToken cancellationToken)
public static async Task<int> Upsert<T>(this DbContext ctx, T item, CancellationToken cancellationToken) where T : class
{
ctx.Attach(item);
ctx.Entry(item).State = EntityState.Modified;
try
{
return await ctx.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateException)
{
ctx.Entry(item).State = EntityState.Added;
return await ctx.SaveChangesAsync(cancellationToken);
}
return await ctx.Upsert(item).RunAsync(cancellationToken);
// ctx.Attach(item);
// ctx.Entry(item).State = EntityState.Modified;
// try
// {
// return await ctx.SaveChangesAsync(cancellationToken);
// }
// catch (DbUpdateException)
// {
// ctx.Entry(item).State = EntityState.Added;
// return await ctx.SaveChangesAsync(cancellationToken);
// }
}
}

View File

@ -0,0 +1,209 @@
using System.Text;
using System.Text.Json;
using BTCPayApp.Core.Attempt2;
using BTCPayApp.Core.Contracts;
using BTCPayApp.Core.Helpers;
using Google.Protobuf;
using Microsoft.EntityFrameworkCore;
using NBitcoin;
using VSSProto;
namespace BTCPayApp.Core.Data;
using System;
using System.Linq;
using System.Security.Cryptography;
using Microsoft.AspNetCore.DataProtection;
public class SingleKeyDataProtector : IDataProtector
{
private readonly byte[] _key;
public SingleKeyDataProtector(byte[] key)
{
if (key.Length != 32) // AES-256 key size
{
throw new ArgumentException("Key length must be 32 bytes.");
}
_key = key;
}
public IDataProtector CreateProtector(string purpose)
{
using var hmac = new HMACSHA256(_key);
var purposeBytes = Encoding.UTF8.GetBytes(purpose);
var key = hmac.ComputeHash(purposeBytes).Take(32).ToArray();
return new SingleKeyDataProtector(key);
}
public byte[] Protect(byte[] plaintext)
{
using var aes = Aes.Create();
aes.Key = _key;
aes.GenerateIV();
byte[] iv = aes.IV;
byte[] encrypted = aes.EncryptCbc(plaintext, iv);
byte[] result = new byte[iv.Length + encrypted.Length];
Buffer.BlockCopy(iv, 0, result, 0, iv.Length);
Buffer.BlockCopy(encrypted, 0, result, iv.Length, encrypted.Length);
return result;
}
public byte[] Unprotect(byte[] protectedData)
{
using var aes = Aes.Create();
aes.Key = _key;
byte[] iv = new byte[16];
byte[] cipherText = new byte[protectedData.Length - iv.Length];
Buffer.BlockCopy(protectedData, 0, iv, 0, iv.Length);
Buffer.BlockCopy(protectedData, iv.Length, cipherText, 0, cipherText.Length);
return aes.DecryptCbc(cipherText, iv);
}
}
public class OutboxProcessor : IScopedHostedService
{
private readonly IDbContextFactory<AppDbContext> _dbContextFactory;
private readonly BTCPayConnectionManager _btcPayConnectionManager;
private readonly ISecureConfigProvider _secureConfigProvider;
public OutboxProcessor(IDbContextFactory<AppDbContext> dbContextFactory, BTCPayConnectionManager btcPayConnectionManager, ISecureConfigProvider secureConfigProvider)
{
_dbContextFactory = dbContextFactory;
_btcPayConnectionManager = btcPayConnectionManager;
_secureConfigProvider = secureConfigProvider;
}
private async Task<IDataProtector> GetDataProtector()
{
var k = await _secureConfigProvider.Get<string>("encryptionKey");
if (k == null)
{
k = Convert.ToHexString(RandomUtils.GetBytes(32)).ToLowerInvariant();
await _secureConfigProvider.Set("encryptionKey", k);
}
return new SingleKeyDataProtector(Convert.FromHexString(k));
}
private async Task<KeyValue?> GetValue(AppDbContext dbContext, Outbox outbox)
{
var k = await _secureConfigProvider.Get<string>("encryptionKey");
if (k == null)
{
k = Convert.ToHexString(RandomUtils.GetBytes(32)).ToLowerInvariant();
await _secureConfigProvider.Set("encryptionKey", k);
}
switch (outbox.Entity)
{
case "Setting":
var setting = await dbContext.Settings.FindAsync(outbox.Key);
if(setting?.Backup is not true)
return null;
return new KeyValue()
{
Key = outbox.Key,
Value = ByteString.CopyFrom(setting.Value),
Version = setting.Version
};
case "Channel":
var channel = await dbContext.LightningChannels.Include(channel1 => channel1.Aliases).SingleOrDefaultAsync(channel1 => channel1.Id == outbox.Key);
if(channel == null)
return null;
var val = JsonSerializer.SerializeToUtf8Bytes(channel);
return new KeyValue()
{
Key = outbox.Key,
Value = ByteString.CopyFrom(val),
Version = channel.Version
};
case "Payment":
var split = outbox.Key.Split('_');
var paymentHash = uint256.Parse(split[0]);
var paymentId = split[1];
var inbound = bool.Parse(split[2]);
var payment = await dbContext.LightningPayments.FindAsync(paymentHash, paymentId, inbound);
if(payment == null)
return null;
var paymentBytes = JsonSerializer.SerializeToUtf8Bytes(payment);
return new KeyValue()
{
Key = outbox.Key,
Value = ByteString.CopyFrom(paymentBytes),
Version = payment.Version
};
default:
throw new ArgumentOutOfRangeException();
}
}
private async Task ProcessOutbox(CancellationToken cancellationToken = default)
{
await using var db = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
var putObjectRequest = new PutObjectRequest();
var outbox = await db.OutboxItems.GroupBy(outbox1 => new {outbox1.Entity, outbox1.Key})
.ToListAsync(cancellationToken: cancellationToken);
foreach (var outboxItemSet in outbox)
{
var orderedEnumerable = outboxItemSet.OrderByDescending(outbox1 => outbox1.Version)
.ThenByDescending(outbox1 => outbox1.ActionType).ToArray();
foreach (var item in orderedEnumerable)
{
if (item.ActionType == OutboxAction.Delete )
{
putObjectRequest.DeleteItems.Add(new KeyValue()
{
Key = item.Entity, Version = item.Version
});
}
else
{
var kv = await GetValue(db, item);
if (kv != null)
{
putObjectRequest.TransactionItems.Add(kv);
break;
}
}
}
db.OutboxItems.RemoveRange(orderedEnumerable);
// Process outbox item
}
}
private CancellationTokenSource _cts = new();
public async Task StartAsync(CancellationToken cancellationToken)
{
_cts = new CancellationTokenSource();
_ = Task.Run(async () =>
{
while (!_cts.Token.IsCancellationRequested)
{
await ProcessOutbox(_cts.Token);
await Task.Delay(2000, _cts.Token);
}
}, _cts.Token);
}
public async Task StopAsync(CancellationToken cancellationToken)
{
await _cts.CancelAsync();
await ProcessOutbox(cancellationToken);
}
}

View File

@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json;
namespace BTCPayApp.Core.Data;

View File

@ -0,0 +1,43 @@
namespace BTCPayApp.Core.Data;
public static class TLVHelper
{
public record TLV(byte Tag, byte[] Value);
public static byte[] Write(List<TLV> tlvList)
{
List<byte> byteArray = new List<byte>();
foreach (var tlv in tlvList)
{
byteArray.Add(tlv.Tag);
byteArray.AddRange(BitConverter.GetBytes(tlv.Value.Length));
byteArray.AddRange(tlv.Value);
}
return byteArray.ToArray();
}
public static List<TLV> Read(byte[] byteArray)
{
var tlvList = new List<TLV>();
var index = 0;
while (index < byteArray.Length)
{
var tag = byteArray[index];
index += 1;
var length = BitConverter.ToInt32(byteArray, index);
index += 4;
var value = new byte[length];
Array.Copy(byteArray, index, value, 0, length);
index += length;
tlvList.Add(new TLV(tag, value));
}
return tlvList;
}
}

View File

@ -1,8 +1,9 @@
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json;
namespace BTCPayApp.Core.Data;
public abstract class VersionedData
{
public ulong Version { get; set; } = 0;
}
public long Version { get; set; } = 0;
}

View File

@ -110,10 +110,10 @@ public class DatabaseConfigProvider: IConfigProvider
return config is null ? default : JsonSerializer.Deserialize<T>(config.Value);
}
public async Task Set<T>(string key, T? value)
public async Task Set<T>(string key, T? value, bool backup)
{
using var releaser = await _lock.LockAsync(key);
_logger.LogDebug("Setting {key} to {value}", key, value);
_logger.LogDebug("Setting {key} to {value} {backup}", key, value, backup? "backup": "no backup");
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
if (value is null)
{
@ -129,8 +129,8 @@ public class DatabaseConfigProvider: IConfigProvider
}
var newValue = typeof(T) == typeof(byte[])? value as byte[]:JsonSerializer.SerializeToUtf8Bytes(value);
var setting = new Setting {Key = key, Value = newValue};
await dbContext.CrappyUpsert(setting, CancellationToken.None);
var setting = new Setting {Key = key, Value = newValue, Backup = backup};
await dbContext.Upsert(setting, CancellationToken.None);
}

View File

@ -297,8 +297,8 @@ var paySecret = new uint256(invoice.payment_secret());
private async Task Payment(AppLightningPayment lightningPayment, CancellationToken cancellationToken = default)
{
await using var context = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
var x = await context.CrappyUpsert(lightningPayment, cancellationToken);
if (x > 0)
var x = await context.Upsert(lightningPayment, cancellationToken);
if (x > 1)//we have triggers that create an outbox record everytime so we need to check for more than 1 record
{
OnPaymentUpdate?.Invoke(this, lightningPayment);
}

View File

@ -11,7 +11,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace BTCPayApp.Core.Migrations
{
[DbContext(typeof(AppDbContext))]
[Migration("20240723141157_triggers")]
[Migration("20240724071302_triggers")]
partial class triggers
{
/// <inheritdoc />
@ -72,7 +72,7 @@ namespace BTCPayApp.Core.Migrations
b
.HasAnnotation("LC_TRIGGER_AFTER_DELETE_APPLIGHTNINGPAYMENT", "CREATE TRIGGER LC_TRIGGER_AFTER_DELETE_APPLIGHTNINGPAYMENT\r\nAFTER DELETE ON \"LightningPayments\"\r\nFOR EACH ROW\r\nBEGIN\r\n INSERT INTO \"OutboxItems\" (\"Entity\", \"Version\", \"Key\", \"ActionType\") SELECT 'Payment', \r\n OLD.\"Version\", \r\n OLD.\"PaymentHash\" || '_' || OLD.\"PaymentId\" || '_' || OLD.\"Inbound\", \r\n 2;\r\nEND;")
.HasAnnotation("LC_TRIGGER_AFTER_INSERT_APPLIGHTNINGPAYMENT", "CREATE TRIGGER LC_TRIGGER_AFTER_INSERT_APPLIGHTNINGPAYMENT\r\nAFTER INSERT ON \"LightningPayments\"\r\nFOR EACH ROW\r\nBEGIN\r\n INSERT INTO \"OutboxItems\" (\"Entity\", \"Version\", \"Key\", \"ActionType\") SELECT 'Payment', \r\n NEW.\"Version\", \r\n NEW.\"PaymentHash\" || '_' || NEW.\"PaymentId\" || '_' || NEW.\"Inbound\", \r\n 0;\r\nEND;")
.HasAnnotation("LC_TRIGGER_AFTER_UPDATE_APPLIGHTNINGPAYMENT", "CREATE TRIGGER LC_TRIGGER_AFTER_UPDATE_APPLIGHTNINGPAYMENT\r\nAFTER UPDATE ON \"LightningPayments\"\r\nFOR EACH ROW\r\nBEGIN\r\n UPDATE \"LightningPayments\"\r\n SET \"Version\" = OLD.\"Version\" + 1\r\n WHERE OLD.\"PaymentHash\" = \"LightningPayments\".\"PaymentHash\";\r\nEND;");
.HasAnnotation("LC_TRIGGER_AFTER_UPDATE_APPLIGHTNINGPAYMENT", "CREATE TRIGGER LC_TRIGGER_AFTER_UPDATE_APPLIGHTNINGPAYMENT\r\nAFTER UPDATE ON \"LightningPayments\"\r\nFOR EACH ROW\r\nBEGIN\r\n UPDATE \"LightningPayments\"\r\n SET \"Version\" = OLD.\"Version\" + 1\r\n WHERE OLD.\"PaymentHash\" = \"LightningPayments\".\"PaymentHash\";\r\n INSERT INTO \"OutboxItems\" (\"Entity\", \"Version\", \"Key\", \"ActionType\") SELECT 'Payment', \r\n OLD.\"Version\" + 1, \r\n NEW.\"PaymentHash\" || '_' || NEW.\"PaymentId\" || '_' || NEW.\"Inbound\", \r\n 1;\r\nEND;");
});
modelBuilder.Entity("BTCPayApp.Core.Data.Channel", b =>
@ -101,7 +101,7 @@ namespace BTCPayApp.Core.Migrations
b
.HasAnnotation("LC_TRIGGER_AFTER_DELETE_CHANNEL", "CREATE TRIGGER LC_TRIGGER_AFTER_DELETE_CHANNEL\r\nAFTER DELETE ON \"LightningChannels\"\r\nFOR EACH ROW\r\nBEGIN\r\n INSERT INTO \"OutboxItems\" (\"Entity\", \"Version\", \"Key\", \"ActionType\") SELECT 'Channel', \r\n OLD.\"Version\", \r\n OLD.\"Id\", \r\n 2;\r\nEND;")
.HasAnnotation("LC_TRIGGER_AFTER_INSERT_CHANNEL", "CREATE TRIGGER LC_TRIGGER_AFTER_INSERT_CHANNEL\r\nAFTER INSERT ON \"LightningChannels\"\r\nFOR EACH ROW\r\nBEGIN\r\n INSERT INTO \"OutboxItems\" (\"Entity\", \"Version\", \"Key\", \"ActionType\") SELECT 'Channel', \r\n NEW.\"Version\", \r\n NEW.\"Id\", \r\n 0;\r\nEND;")
.HasAnnotation("LC_TRIGGER_AFTER_UPDATE_CHANNEL", "CREATE TRIGGER LC_TRIGGER_AFTER_UPDATE_CHANNEL\r\nAFTER UPDATE ON \"LightningChannels\"\r\nFOR EACH ROW\r\nBEGIN\r\n UPDATE \"LightningChannels\"\r\n SET \"Version\" = OLD.\"Version\" + 1\r\n WHERE OLD.\"Id\" = \"LightningChannels\".\"Id\";\r\nEND;");
.HasAnnotation("LC_TRIGGER_AFTER_UPDATE_CHANNEL", "CREATE TRIGGER LC_TRIGGER_AFTER_UPDATE_CHANNEL\r\nAFTER UPDATE ON \"LightningChannels\"\r\nFOR EACH ROW\r\nBEGIN\r\n UPDATE \"LightningChannels\"\r\n SET \"Version\" = OLD.\"Version\" + 1\r\n WHERE OLD.\"Id\" = \"LightningChannels\".\"Id\";\r\n INSERT INTO \"OutboxItems\" (\"Entity\", \"Version\", \"Key\", \"ActionType\") SELECT 'Channel', \r\n OLD.\"Version\" + 1, \r\n NEW.\"Id\", \r\n 1;\r\nEND;");
});
modelBuilder.Entity("BTCPayApp.Core.Data.ChannelAlias", b =>
@ -141,7 +141,7 @@ namespace BTCPayApp.Core.Migrations
b.Property<DateTimeOffset>("Timestamp")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValueSql("date()");
.HasDefaultValueSql("datetime('now')");
b.HasKey("Entity", "Key", "ActionType", "Version");
@ -177,7 +177,7 @@ namespace BTCPayApp.Core.Migrations
b
.HasAnnotation("LC_TRIGGER_AFTER_DELETE_SETTING", "CREATE TRIGGER LC_TRIGGER_AFTER_DELETE_SETTING\r\nAFTER DELETE ON \"Settings\"\r\nFOR EACH ROW\r\nWHEN \r\n \r\n OLD.\"Backup\" IS TRUE\r\nBEGIN\r\n INSERT INTO \"OutboxItems\" (\"Entity\", \"Version\", \"Key\", \"ActionType\") SELECT 'Setting', \r\n OLD.\"Version\", \r\n OLD.\"Key\", \r\n 2;\r\nEND;")
.HasAnnotation("LC_TRIGGER_AFTER_INSERT_SETTING", "CREATE TRIGGER LC_TRIGGER_AFTER_INSERT_SETTING\r\nAFTER INSERT ON \"Settings\"\r\nFOR EACH ROW\r\nWHEN \r\n \r\n NEW.\"Backup\" IS TRUE\r\nBEGIN\r\n INSERT INTO \"OutboxItems\" (\"Entity\", \"Version\", \"Key\", \"ActionType\") SELECT 'Setting', \r\n NEW.\"Version\", \r\n NEW.\"Key\", \r\n 0;\r\nEND;")
.HasAnnotation("LC_TRIGGER_AFTER_UPDATE_SETTING", "CREATE TRIGGER LC_TRIGGER_AFTER_UPDATE_SETTING\r\nAFTER UPDATE ON \"Settings\"\r\nFOR EACH ROW\r\nBEGIN\r\n UPDATE \"Settings\"\r\n SET \"Version\" = OLD.\"Version\" + 1\r\n WHERE OLD.\"Key\" = \"Settings\".\"Key\";\r\nEND;");
.HasAnnotation("LC_TRIGGER_AFTER_UPDATE_SETTING", "CREATE TRIGGER LC_TRIGGER_AFTER_UPDATE_SETTING\r\nAFTER UPDATE ON \"Settings\"\r\nFOR EACH ROW\r\nBEGIN\r\n UPDATE \"Settings\"\r\n SET \"Version\" = OLD.\"Version\" + 1\r\n WHERE OLD.\"Key\" = \"Settings\".\"Key\";\r\n INSERT INTO \"OutboxItems\" (\"Entity\", \"Version\", \"Key\", \"ActionType\") SELECT 'Setting', \r\n OLD.\"Version\" + 1, \r\n NEW.\"Key\", \r\n 1;\r\nEND;");
});
modelBuilder.Entity("BTCPayApp.Core.Data.ChannelAlias", b =>

View File

@ -70,7 +70,7 @@ namespace BTCPayApp.Core.Migrations
Key = table.Column<string>(type: "TEXT", nullable: false),
Entity = table.Column<string>(type: "TEXT", nullable: false),
Version = table.Column<ulong>(type: "INTEGER", nullable: false),
Timestamp = table.Column<DateTimeOffset>(type: "TEXT", nullable: false, defaultValueSql: "date()")
Timestamp = table.Column<DateTimeOffset>(type: "TEXT", nullable: false, defaultValueSql: "datetime('now')")
},
constraints: table =>
{
@ -86,19 +86,19 @@ namespace BTCPayApp.Core.Migrations
migrationBuilder.Sql("CREATE TRIGGER LC_TRIGGER_AFTER_INSERT_APPLIGHTNINGPAYMENT\r\nAFTER INSERT ON \"LightningPayments\"\r\nFOR EACH ROW\r\nBEGIN\r\n INSERT INTO \"OutboxItems\" (\"Entity\", \"Version\", \"Key\", \"ActionType\") SELECT 'Payment', \r\n NEW.\"Version\", \r\n NEW.\"PaymentHash\" || '_' || NEW.\"PaymentId\" || '_' || NEW.\"Inbound\", \r\n 0;\r\nEND;");
migrationBuilder.Sql("CREATE TRIGGER LC_TRIGGER_AFTER_UPDATE_APPLIGHTNINGPAYMENT\r\nAFTER UPDATE ON \"LightningPayments\"\r\nFOR EACH ROW\r\nBEGIN\r\n UPDATE \"LightningPayments\"\r\n SET \"Version\" = OLD.\"Version\" + 1\r\n WHERE OLD.\"PaymentHash\" = \"LightningPayments\".\"PaymentHash\";\r\nEND;");
migrationBuilder.Sql("CREATE TRIGGER LC_TRIGGER_AFTER_UPDATE_APPLIGHTNINGPAYMENT\r\nAFTER UPDATE ON \"LightningPayments\"\r\nFOR EACH ROW\r\nBEGIN\r\n UPDATE \"LightningPayments\"\r\n SET \"Version\" = OLD.\"Version\" + 1\r\n WHERE OLD.\"PaymentHash\" = \"LightningPayments\".\"PaymentHash\";\r\n INSERT INTO \"OutboxItems\" (\"Entity\", \"Version\", \"Key\", \"ActionType\") SELECT 'Payment', \r\n OLD.\"Version\" + 1, \r\n NEW.\"PaymentHash\" || '_' || NEW.\"PaymentId\" || '_' || NEW.\"Inbound\", \r\n 1;\r\nEND;");
migrationBuilder.Sql("CREATE TRIGGER LC_TRIGGER_AFTER_DELETE_CHANNEL\r\nAFTER DELETE ON \"LightningChannels\"\r\nFOR EACH ROW\r\nBEGIN\r\n INSERT INTO \"OutboxItems\" (\"Entity\", \"Version\", \"Key\", \"ActionType\") SELECT 'Channel', \r\n OLD.\"Version\", \r\n OLD.\"Id\", \r\n 2;\r\nEND;");
migrationBuilder.Sql("CREATE TRIGGER LC_TRIGGER_AFTER_INSERT_CHANNEL\r\nAFTER INSERT ON \"LightningChannels\"\r\nFOR EACH ROW\r\nBEGIN\r\n INSERT INTO \"OutboxItems\" (\"Entity\", \"Version\", \"Key\", \"ActionType\") SELECT 'Channel', \r\n NEW.\"Version\", \r\n NEW.\"Id\", \r\n 0;\r\nEND;");
migrationBuilder.Sql("CREATE TRIGGER LC_TRIGGER_AFTER_UPDATE_CHANNEL\r\nAFTER UPDATE ON \"LightningChannels\"\r\nFOR EACH ROW\r\nBEGIN\r\n UPDATE \"LightningChannels\"\r\n SET \"Version\" = OLD.\"Version\" + 1\r\n WHERE OLD.\"Id\" = \"LightningChannels\".\"Id\";\r\nEND;");
migrationBuilder.Sql("CREATE TRIGGER LC_TRIGGER_AFTER_UPDATE_CHANNEL\r\nAFTER UPDATE ON \"LightningChannels\"\r\nFOR EACH ROW\r\nBEGIN\r\n UPDATE \"LightningChannels\"\r\n SET \"Version\" = OLD.\"Version\" + 1\r\n WHERE OLD.\"Id\" = \"LightningChannels\".\"Id\";\r\n INSERT INTO \"OutboxItems\" (\"Entity\", \"Version\", \"Key\", \"ActionType\") SELECT 'Channel', \r\n OLD.\"Version\" + 1, \r\n NEW.\"Id\", \r\n 1;\r\nEND;");
migrationBuilder.Sql("CREATE TRIGGER LC_TRIGGER_AFTER_DELETE_SETTING\r\nAFTER DELETE ON \"Settings\"\r\nFOR EACH ROW\r\nWHEN \r\n \r\n OLD.\"Backup\" IS TRUE\r\nBEGIN\r\n INSERT INTO \"OutboxItems\" (\"Entity\", \"Version\", \"Key\", \"ActionType\") SELECT 'Setting', \r\n OLD.\"Version\", \r\n OLD.\"Key\", \r\n 2;\r\nEND;");
migrationBuilder.Sql("CREATE TRIGGER LC_TRIGGER_AFTER_INSERT_SETTING\r\nAFTER INSERT ON \"Settings\"\r\nFOR EACH ROW\r\nWHEN \r\n \r\n NEW.\"Backup\" IS TRUE\r\nBEGIN\r\n INSERT INTO \"OutboxItems\" (\"Entity\", \"Version\", \"Key\", \"ActionType\") SELECT 'Setting', \r\n NEW.\"Version\", \r\n NEW.\"Key\", \r\n 0;\r\nEND;");
migrationBuilder.Sql("CREATE TRIGGER LC_TRIGGER_AFTER_UPDATE_SETTING\r\nAFTER UPDATE ON \"Settings\"\r\nFOR EACH ROW\r\nBEGIN\r\n UPDATE \"Settings\"\r\n SET \"Version\" = OLD.\"Version\" + 1\r\n WHERE OLD.\"Key\" = \"Settings\".\"Key\";\r\nEND;");
migrationBuilder.Sql("CREATE TRIGGER LC_TRIGGER_AFTER_UPDATE_SETTING\r\nAFTER UPDATE ON \"Settings\"\r\nFOR EACH ROW\r\nBEGIN\r\n UPDATE \"Settings\"\r\n SET \"Version\" = OLD.\"Version\" + 1\r\n WHERE OLD.\"Key\" = \"Settings\".\"Key\";\r\n INSERT INTO \"OutboxItems\" (\"Entity\", \"Version\", \"Key\", \"ActionType\") SELECT 'Setting', \r\n OLD.\"Version\" + 1, \r\n NEW.\"Key\", \r\n 1;\r\nEND;");
}
/// <inheritdoc />

View File

@ -69,7 +69,7 @@ namespace BTCPayApp.Core.Migrations
b
.HasAnnotation("LC_TRIGGER_AFTER_DELETE_APPLIGHTNINGPAYMENT", "CREATE TRIGGER LC_TRIGGER_AFTER_DELETE_APPLIGHTNINGPAYMENT\r\nAFTER DELETE ON \"LightningPayments\"\r\nFOR EACH ROW\r\nBEGIN\r\n INSERT INTO \"OutboxItems\" (\"Entity\", \"Version\", \"Key\", \"ActionType\") SELECT 'Payment', \r\n OLD.\"Version\", \r\n OLD.\"PaymentHash\" || '_' || OLD.\"PaymentId\" || '_' || OLD.\"Inbound\", \r\n 2;\r\nEND;")
.HasAnnotation("LC_TRIGGER_AFTER_INSERT_APPLIGHTNINGPAYMENT", "CREATE TRIGGER LC_TRIGGER_AFTER_INSERT_APPLIGHTNINGPAYMENT\r\nAFTER INSERT ON \"LightningPayments\"\r\nFOR EACH ROW\r\nBEGIN\r\n INSERT INTO \"OutboxItems\" (\"Entity\", \"Version\", \"Key\", \"ActionType\") SELECT 'Payment', \r\n NEW.\"Version\", \r\n NEW.\"PaymentHash\" || '_' || NEW.\"PaymentId\" || '_' || NEW.\"Inbound\", \r\n 0;\r\nEND;")
.HasAnnotation("LC_TRIGGER_AFTER_UPDATE_APPLIGHTNINGPAYMENT", "CREATE TRIGGER LC_TRIGGER_AFTER_UPDATE_APPLIGHTNINGPAYMENT\r\nAFTER UPDATE ON \"LightningPayments\"\r\nFOR EACH ROW\r\nBEGIN\r\n UPDATE \"LightningPayments\"\r\n SET \"Version\" = OLD.\"Version\" + 1\r\n WHERE OLD.\"PaymentHash\" = \"LightningPayments\".\"PaymentHash\";\r\nEND;");
.HasAnnotation("LC_TRIGGER_AFTER_UPDATE_APPLIGHTNINGPAYMENT", "CREATE TRIGGER LC_TRIGGER_AFTER_UPDATE_APPLIGHTNINGPAYMENT\r\nAFTER UPDATE ON \"LightningPayments\"\r\nFOR EACH ROW\r\nBEGIN\r\n UPDATE \"LightningPayments\"\r\n SET \"Version\" = OLD.\"Version\" + 1\r\n WHERE OLD.\"PaymentHash\" = \"LightningPayments\".\"PaymentHash\";\r\n INSERT INTO \"OutboxItems\" (\"Entity\", \"Version\", \"Key\", \"ActionType\") SELECT 'Payment', \r\n OLD.\"Version\" + 1, \r\n NEW.\"PaymentHash\" || '_' || NEW.\"PaymentId\" || '_' || NEW.\"Inbound\", \r\n 1;\r\nEND;");
});
modelBuilder.Entity("BTCPayApp.Core.Data.Channel", b =>
@ -98,7 +98,7 @@ namespace BTCPayApp.Core.Migrations
b
.HasAnnotation("LC_TRIGGER_AFTER_DELETE_CHANNEL", "CREATE TRIGGER LC_TRIGGER_AFTER_DELETE_CHANNEL\r\nAFTER DELETE ON \"LightningChannels\"\r\nFOR EACH ROW\r\nBEGIN\r\n INSERT INTO \"OutboxItems\" (\"Entity\", \"Version\", \"Key\", \"ActionType\") SELECT 'Channel', \r\n OLD.\"Version\", \r\n OLD.\"Id\", \r\n 2;\r\nEND;")
.HasAnnotation("LC_TRIGGER_AFTER_INSERT_CHANNEL", "CREATE TRIGGER LC_TRIGGER_AFTER_INSERT_CHANNEL\r\nAFTER INSERT ON \"LightningChannels\"\r\nFOR EACH ROW\r\nBEGIN\r\n INSERT INTO \"OutboxItems\" (\"Entity\", \"Version\", \"Key\", \"ActionType\") SELECT 'Channel', \r\n NEW.\"Version\", \r\n NEW.\"Id\", \r\n 0;\r\nEND;")
.HasAnnotation("LC_TRIGGER_AFTER_UPDATE_CHANNEL", "CREATE TRIGGER LC_TRIGGER_AFTER_UPDATE_CHANNEL\r\nAFTER UPDATE ON \"LightningChannels\"\r\nFOR EACH ROW\r\nBEGIN\r\n UPDATE \"LightningChannels\"\r\n SET \"Version\" = OLD.\"Version\" + 1\r\n WHERE OLD.\"Id\" = \"LightningChannels\".\"Id\";\r\nEND;");
.HasAnnotation("LC_TRIGGER_AFTER_UPDATE_CHANNEL", "CREATE TRIGGER LC_TRIGGER_AFTER_UPDATE_CHANNEL\r\nAFTER UPDATE ON \"LightningChannels\"\r\nFOR EACH ROW\r\nBEGIN\r\n UPDATE \"LightningChannels\"\r\n SET \"Version\" = OLD.\"Version\" + 1\r\n WHERE OLD.\"Id\" = \"LightningChannels\".\"Id\";\r\n INSERT INTO \"OutboxItems\" (\"Entity\", \"Version\", \"Key\", \"ActionType\") SELECT 'Channel', \r\n OLD.\"Version\" + 1, \r\n NEW.\"Id\", \r\n 1;\r\nEND;");
});
modelBuilder.Entity("BTCPayApp.Core.Data.ChannelAlias", b =>
@ -138,7 +138,7 @@ namespace BTCPayApp.Core.Migrations
b.Property<DateTimeOffset>("Timestamp")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValueSql("date()");
.HasDefaultValueSql("datetime('now')");
b.HasKey("Entity", "Key", "ActionType", "Version");
@ -174,7 +174,7 @@ namespace BTCPayApp.Core.Migrations
b
.HasAnnotation("LC_TRIGGER_AFTER_DELETE_SETTING", "CREATE TRIGGER LC_TRIGGER_AFTER_DELETE_SETTING\r\nAFTER DELETE ON \"Settings\"\r\nFOR EACH ROW\r\nWHEN \r\n \r\n OLD.\"Backup\" IS TRUE\r\nBEGIN\r\n INSERT INTO \"OutboxItems\" (\"Entity\", \"Version\", \"Key\", \"ActionType\") SELECT 'Setting', \r\n OLD.\"Version\", \r\n OLD.\"Key\", \r\n 2;\r\nEND;")
.HasAnnotation("LC_TRIGGER_AFTER_INSERT_SETTING", "CREATE TRIGGER LC_TRIGGER_AFTER_INSERT_SETTING\r\nAFTER INSERT ON \"Settings\"\r\nFOR EACH ROW\r\nWHEN \r\n \r\n NEW.\"Backup\" IS TRUE\r\nBEGIN\r\n INSERT INTO \"OutboxItems\" (\"Entity\", \"Version\", \"Key\", \"ActionType\") SELECT 'Setting', \r\n NEW.\"Version\", \r\n NEW.\"Key\", \r\n 0;\r\nEND;")
.HasAnnotation("LC_TRIGGER_AFTER_UPDATE_SETTING", "CREATE TRIGGER LC_TRIGGER_AFTER_UPDATE_SETTING\r\nAFTER UPDATE ON \"Settings\"\r\nFOR EACH ROW\r\nBEGIN\r\n UPDATE \"Settings\"\r\n SET \"Version\" = OLD.\"Version\" + 1\r\n WHERE OLD.\"Key\" = \"Settings\".\"Key\";\r\nEND;");
.HasAnnotation("LC_TRIGGER_AFTER_UPDATE_SETTING", "CREATE TRIGGER LC_TRIGGER_AFTER_UPDATE_SETTING\r\nAFTER UPDATE ON \"Settings\"\r\nFOR EACH ROW\r\nBEGIN\r\n UPDATE \"Settings\"\r\n SET \"Version\" = OLD.\"Version\" + 1\r\n WHERE OLD.\"Key\" = \"Settings\".\"Key\";\r\n INSERT INTO \"OutboxItems\" (\"Entity\", \"Version\", \"Key\", \"ActionType\") SELECT 'Setting', \r\n OLD.\"Version\" + 1, \r\n NEW.\"Key\", \r\n 1;\r\nEND;");
});
modelBuilder.Entity("BTCPayApp.Core.Data.ChannelAlias", b =>

View File

@ -1,4 +1,5 @@
using BTCPayApp.CommonServer;
using System.Xml.Linq;
using BTCPayApp.CommonServer;
using BTCPayApp.Core.Attempt2;
using BTCPayApp.Core.Auth;
using BTCPayApp.Core.Contracts;
@ -6,6 +7,8 @@ using BTCPayApp.Core.Data;
using BTCPayApp.Core.LDK;
using Laraue.EfCoreTriggers.SqlLite.Extensions;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.DataProtection.Repositories;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
@ -39,12 +42,10 @@ public static class StartupExtensions
serviceCollection.AddSingleton(sp => (IAccountManager)sp.GetRequiredService<AuthenticationStateProvider>());
serviceCollection.AddSingleton<IConfigProvider, DatabaseConfigProvider>();
serviceCollection.AddLDK();
return serviceCollection;
}
}
public class AppDatabaseMigrator: IHostedService
{
private readonly ILogger<AppDatabaseMigrator> _logger;

View File

@ -15,7 +15,6 @@ public static class StartupExtensions
serviceCollection.AddDataProtection(options =>
{
options.ApplicationDiscriminator = "BTCPayApp";
});
serviceCollection.AddSingleton<IDataDirectoryProvider, DesktopDataDirectoryProvider>();
// serviceCollection.AddSingleton<IConfigProvider, DesktopConfigProvider>();
@ -25,56 +24,22 @@ public static class StartupExtensions
}
}
public class DesktopSecureConfigProvider: DesktopConfigProvider, ISecureConfigProvider
public class DesktopSecureConfigProvider: ISecureConfigProvider
{
private readonly IDataProtector _dataProtector;
public DesktopSecureConfigProvider(IDataDirectoryProvider directoryProvider, IDataProtectionProvider dataProtectionProvider) : base(directoryProvider)
public DesktopSecureConfigProvider(IDataDirectoryProvider directoryProvider, IDataProtectionProvider dataProtectionProvider)
{
_dataProtector = dataProtectionProvider.CreateProtector("SecureConfig");
}
protected override Task<string> ReadFromRaw(string str) => Task.FromResult(_dataProtector.Unprotect(str));
protected override Task<string> WriteFromRaw(string str) => Task.FromResult(_dataProtector.Protect(str));
}
public class FingerprintProvider: IFingerprint
{
public Task<FingerprintAvailability> GetAvailabilityAsync(bool allowAlternativeAuthentication = false)
{
return Task.FromResult(FingerprintAvailability.NoImplementation);
}
public Task<bool> IsAvailableAsync(bool allowAlternativeAuthentication = false)
{
return Task.FromResult(false);
}
public Task<FingerprintAuthenticationResult> AuthenticateAsync(AuthenticationRequestConfiguration authRequestConfig,
CancellationToken cancellationToken = new CancellationToken())
{
throw new NotImplementedException();
}
public Task<AuthenticationType> GetAuthenticationTypeAsync()
{
throw new NotImplementedException();
}
}
public class DesktopConfigProvider : IConfigProvider
{
private readonly Task<string> _configDir;
public DesktopConfigProvider(IDataDirectoryProvider directoryProvider)
{
_configDir = directoryProvider.GetAppDataDirectory().ContinueWith(task =>
{
var res = Path.Combine(task.Result, "config");
Directory.CreateDirectory(res);
return res;
var res = Path.Combine(task.Result, "config");
Directory.CreateDirectory(res);
return res;
});
}
private readonly Task<string> _configDir;
public async Task<T?> Get<T>(string key)
{
@ -88,8 +53,6 @@ public class DesktopConfigProvider : IConfigProvider
return JsonSerializer.Deserialize<T>(json);
}
protected virtual Task<string> ReadFromRaw(string str) => Task.FromResult(str);
protected virtual Task<string> WriteFromRaw(string str) => Task.FromResult(str);
public async Task Set<T>(string key, T? value)
{
@ -117,8 +80,34 @@ public class DesktopConfigProvider : IConfigProvider
}
return Directory.GetFiles(dir, $"{prefix}*").Select(Path.GetFileName).Where(p => p?.StartsWith(prefix) is true)!;
}
protected Task<string> ReadFromRaw(string str) => Task.FromResult(_dataProtector.Unprotect(str));
protected Task<string> WriteFromRaw(string str) => Task.FromResult(_dataProtector.Protect(str));
}
public class FingerprintProvider: IFingerprint
{
public Task<FingerprintAvailability> GetAvailabilityAsync(bool allowAlternativeAuthentication = false)
{
return Task.FromResult(FingerprintAvailability.NoImplementation);
}
public Task<bool> IsAvailableAsync(bool allowAlternativeAuthentication = false)
{
return Task.FromResult(false);
}
public Task<FingerprintAuthenticationResult> AuthenticateAsync(AuthenticationRequestConfiguration authRequestConfig,
CancellationToken cancellationToken = new CancellationToken())
{
throw new NotImplementedException();
}
public Task<AuthenticationType> GetAuthenticationTypeAsync()
{
throw new NotImplementedException();
}
}
public class DesktopDataDirectoryProvider : IDataDirectoryProvider
{
private readonly IConfiguration _configuration;

View File

@ -77,7 +77,7 @@
public async Task HandleValidSubmit()
{
_config!.Passcode = Model.NewPasscode;
await ConfigProvider.Set(BTCPayAppConfig.Key, _config);
await ConfigProvider.Set(BTCPayAppConfig.Key, _config, true);
NavigationManager.NavigateTo(Routes.Settings);
}

View File

@ -209,7 +209,7 @@
if (HasPasscode)
{
_config!.Passcode = null;
await ConfigProvider.Set(BTCPayAppConfig.Key, _config);
await ConfigProvider.Set(BTCPayAppConfig.Key, _config, true);
}
}

View File

@ -33,7 +33,7 @@ public class StateMiddleware(
uiStateFeature.StateChanged += async (sender, args) =>
{
var state = (UIState)uiStateFeature.GetState() with { Instance = null };
await configProvider.Set(UiStateConfigKey, state);
await configProvider.Set(UiStateConfigKey, state, false);
};
store.Initialized.ContinueWith(task => ListenIn(dispatcher));

View File

@ -10,10 +10,10 @@ public class HttpVSSAPIClient : IVSSAPI
private readonly Uri _endpoint;
private readonly HttpClient _httpClient;
private const string GET_OBJECT = "getObject";
private const string PUT_OBJECTS = "putObjects";
private const string DELETE_OBJECT = "deleteObject";
private const string LIST_KEY_VERSIONS = "listKeyVersions";
public const string GET_OBJECT = "getObject";
public const string PUT_OBJECTS = "putObjects";
public const string DELETE_OBJECT = "deleteObject";
public const string LIST_KEY_VERSIONS = "listKeyVersions";
public HttpVSSAPIClient(Uri endpoint, HttpClient? httpClient = null)
{
_endpoint = endpoint;

View File

@ -18,9 +18,9 @@ public class ConcurrentMultiDictionary<TKey, TValue>
private readonly ConcurrentDictionary<TKey, Bag> _dictionary;
public ConcurrentMultiDictionary()
public ConcurrentMultiDictionary(IEqualityComparer<TKey> invariantCultureIgnoreCase = null)
{
_dictionary = new ConcurrentDictionary<TKey, Bag>();
_dictionary = new ConcurrentDictionary<TKey, Bag>(invariantCultureIgnoreCase);
}
public int Count => _dictionary.Count;
@ -41,7 +41,7 @@ public class ConcurrentMultiDictionary<TKey, TValue>
public bool AddOrReplace(TKey key, TValue value)
{
Remove(key, value);
Remove(key, value, out _);
return Add(key, value);
}
@ -60,8 +60,9 @@ public class ConcurrentMultiDictionary<TKey, TValue>
items = null;
return false;
}
public bool Remove(TKey key, TValue value)
public bool Remove(TKey key, TValue value, out bool keyRemoved)
{
keyRemoved = false;
var spinWait = new SpinWait();
while (true)
{
@ -82,7 +83,7 @@ public class ConcurrentMultiDictionary<TKey, TValue>
}
}
if (spinAndRetry) { spinWait.SpinOnce(); continue; }
bool keyRemoved = _dictionary.TryRemove(key, out var currentBag);
keyRemoved = _dictionary.TryRemove(key, out var currentBag);
Debug.Assert(keyRemoved, $"Key {key} was not removed");
Debug.Assert(bag == currentBag, $"Removed wrong bag");
return true;
@ -134,6 +135,11 @@ public class ConcurrentMultiDictionary<TKey, TValue>
{
return _dictionary.Keys.Any(key => Contains(key, value));
}
public bool ContainsValue(TKey key, TValue value)
{
return Contains(key, value);
}
public IEnumerable<TKey> GetKeysContainingValue(IEnumerable<TValue> value)
{
return _dictionary.Keys.Where(key => Contains(key, value));
@ -142,4 +148,20 @@ public class ConcurrentMultiDictionary<TKey, TValue>
{
return _dictionary.Keys.Where(key => Contains(key, value));
}
public bool RemoveValue(TValue value, out TKey[] keysRemoved)
{
List<TKey> keys = [];
var anyValueRemoved = false;
foreach (var key in GetKeysContainingValue(value))
{
anyValueRemoved = Remove(key, value, out var keyRemoved) || anyValueRemoved;
if (keyRemoved)
{
keys.Add(key);
}
}
keysRemoved = keys.ToArray();
return anyValueRemoved;
}
}

View File

@ -4,6 +4,7 @@ using VSSProto;
namespace BTCPayApp.VSS;
public class VSSApiEncryptorClient: IVSSAPI
{
private readonly IVSSAPI _vssApi;