From a75e6b9929cc10c5a06d898f4f40f0af2241d87f Mon Sep 17 00:00:00 2001 From: Kukks Date: Fri, 21 Jun 2024 08:24:18 +0200 Subject: [PATCH 01/14] jit wip subm --- BTCPayApp.Core/Attempt2/LDKNode.cs | 18 +- BTCPayApp.Core/LDK/LDKExtensions.cs | 4 + BTCPayApp.Core/LDK/PaymentsManager.cs | 58 +++- BTCPayApp.Core/LSP/JIT/BTCPayJIT.cs | 14 +- BTCPayApp.Core/LSP/JIT/Flow2Client.cs | 275 ++++++++++++++++++ BTCPayApp.Core/LSP/JIT/FlowFeeRequest.cs | 21 ++ BTCPayApp.Core/LSP/JIT/FlowFeeResponse.cs | 9 + BTCPayApp.Core/LSP/JIT/FlowInfoResponse.cs | 31 ++ BTCPayApp.Core/LSP/JIT/FlowProposalRequest.cs | 18 ++ .../LSP/JIT/FlowProposalResponse.cs | 8 + BTCPayApp.Core/LSP/JIT/IJITService.cs | 11 + 11 files changed, 449 insertions(+), 18 deletions(-) create mode 100644 BTCPayApp.Core/LSP/JIT/Flow2Client.cs create mode 100644 BTCPayApp.Core/LSP/JIT/FlowFeeRequest.cs create mode 100644 BTCPayApp.Core/LSP/JIT/FlowFeeResponse.cs create mode 100644 BTCPayApp.Core/LSP/JIT/FlowInfoResponse.cs create mode 100644 BTCPayApp.Core/LSP/JIT/FlowProposalRequest.cs create mode 100644 BTCPayApp.Core/LSP/JIT/FlowProposalResponse.cs create mode 100644 BTCPayApp.Core/LSP/JIT/IJITService.cs diff --git a/BTCPayApp.Core/Attempt2/LDKNode.cs b/BTCPayApp.Core/Attempt2/LDKNode.cs index 885b8c1..6059d52 100644 --- a/BTCPayApp.Core/Attempt2/LDKNode.cs +++ b/BTCPayApp.Core/Attempt2/LDKNode.cs @@ -3,6 +3,7 @@ using BTCPayApp.Core.Data; using BTCPayApp.Core.Helpers; using BTCPayApp.Core.LDK; +using BTCPayApp.Core.LSP.JIT; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.DependencyInjection; @@ -98,6 +99,18 @@ public async Task OpenChannel(Money amount, PubKey no } } + + public async Task GetJITLSPService() + { + var config = await GetConfig(); + var lsp = config.JITLSP; + if(lsp is null) + { + return null; + } + var jits = ServiceProvider.GetServices(); + return jits.FirstOrDefault(jit => jit.ProviderName == lsp); + } } public partial class LDKNode : IAsyncDisposable, IHostedService, IDisposable @@ -187,6 +200,7 @@ private async Task UpdateConfig(LightningConfig config) await _started.Task; await _configProvider.Set(LightningConfig.Key, config); _config = config; + ConfigUpdated?.Invoke(this, config); } @@ -366,9 +380,9 @@ await context.LightningChannels.AddAsync(new Channel() } - public async Task Peer(string toString, PeerInfo? value) + public async Task Peer(PubKey key, PeerInfo? value) { - toString = toString.ToLowerInvariant(); + var toString = key.ToString().ToLowerInvariant(); var config = await GetConfig(); if (value is null) { diff --git a/BTCPayApp.Core/LDK/LDKExtensions.cs b/BTCPayApp.Core/LDK/LDKExtensions.cs index 66b6ed9..06d880f 100644 --- a/BTCPayApp.Core/LDK/LDKExtensions.cs +++ b/BTCPayApp.Core/LDK/LDKExtensions.cs @@ -3,6 +3,7 @@ using BTCPayApp.Core.Attempt2; using BTCPayApp.Core.Contracts; using BTCPayApp.Core.Helpers; +using BTCPayApp.Core.LSP.JIT; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using NBitcoin; @@ -300,6 +301,9 @@ public static IServiceCollection AddLDK(this IServiceCollection services) ProbabilisticScoringFeeParameters.with_default())); services.AddScoped(provider => provider.GetRequiredService().as_Router()); + + services.AddScoped(); + return services; } diff --git a/BTCPayApp.Core/LDK/PaymentsManager.cs b/BTCPayApp.Core/LDK/PaymentsManager.cs index c4fd5a6..f7689ef 100644 --- a/BTCPayApp.Core/LDK/PaymentsManager.cs +++ b/BTCPayApp.Core/LDK/PaymentsManager.cs @@ -1,10 +1,14 @@ using System.Security.Cryptography; +using System.Text.Json; +using BTCPayApp.Core.Attempt2; using BTCPayApp.Core.Data; using BTCPayApp.Core.Helpers; +using BTCPayApp.Core.LSP.JIT; using BTCPayServer.Lightning; using Microsoft.EntityFrameworkCore; using NBitcoin; using org.ldk.structs; +using org.ldk.util; // using BTCPayServer.Lightning; using LightningPayment = BTCPayApp.CommonServer.Models.LightningPayment; @@ -16,26 +20,31 @@ public class PaymentsManager : ILDKEventHandler, ILDKEventHandler { + public const string LightningPaymentDescriptionKey = "DescriptionHash"; + public const string LightningPaymentExpiryKey = "Expiry"; + private readonly IDbContextFactory _dbContextFactory; private readonly ChannelManager _channelManager; private readonly Logger _logger; private readonly NodeSigner _nodeSigner; private readonly Network _network; + private readonly LDKNode _ldkNode; public PaymentsManager( IDbContextFactory dbContextFactory, ChannelManager channelManager, Logger logger, NodeSigner nodeSigner, - Network network - ) + Network network, + LDKNode ldkNode) { _dbContextFactory = dbContextFactory; _channelManager = channelManager; _logger = logger; _nodeSigner = nodeSigner; _network = network; + _ldkNode = ldkNode; } public async Task> List( @@ -47,6 +56,24 @@ public async Task> List( .ToListAsync(cancellationToken: cancellationToken))!; } + private Bolt11Invoice CreateInvoice(long? amt, int expirySeconds, byte[] descHash) + { + var keyMaterial = _nodeSigner.get_inbound_payment_key_material(); + var preimage = RandomUtils.GetBytes(32); + var paymentHash = SHA256.HashData(preimage); + var expandedKey = ExpandedKey.of(keyMaterial); + var inboundPayment = _channelManager.create_inbound_payment_for_hash(paymentHash, + amt is null ? Option_u64Z.none() : Option_u64Z.some(amt.Value), expirySeconds, Option_u16Z.none())); + var paymentSecret = inboundPayment is Result_ThirtyTwoBytesNoneZ.Result_ThirtyTwoBytesNoneZ_OK ok + ? ok.res + : throw new Exception("Error creating inbound payment"); + + _nodeSigner. + var invoice = Bolt11Invoice.from_signed(_channelManager, _nodeSigner, _logger, _network.GetLdkCurrency(), + + + } + public async Task RequestPayment(LightMoney amount, TimeSpan expiry, uint256 descriptionHash) { var amt = amount == LightMoney.Zero ? Option_u64Z.none() : Option_u64Z.some(amount.MilliSatoshi); @@ -58,9 +85,8 @@ public async Task RequestPayment(LightMoney amount, TimeSpan e // _network.GetLdkCurrency(), amt, description, epoch, (int) Math.Ceiling(expiry.TotalSeconds), // paymentHash, Option_u16Z.none()); - var descHashBytes = Sha256.from_bytes(descriptionHash.ToBytes()); - + var lsp = await _ldkNode.GetJITLSPService(); var result = await Task.Run(() => org.ldk.util.UtilMethods.create_invoice_from_channelmanager_with_description_hash_and_duration_since_epoch( _channelManager, _nodeSigner, _logger, @@ -71,6 +97,11 @@ public async Task RequestPayment(LightMoney amount, TimeSpan e throw new Exception(err.err.to_str()); } + var keyMaterial = _nodeSigner.get_inbound_payment_key_material(); + var expandedKey = ExpandedKey.of(keyMaterial); + _channelManager.create_inbound_payment_for_hash() + UtilMethods.create_invoice_from_channelmanager() + var invoice = ((Result_Bolt11InvoiceSignOrCreationErrorZ.Result_Bolt11InvoiceSignOrCreationErrorZ_OK) result) .res; var preimageResult = _channelManager.get_payment_preimage(invoice.payment_hash(), invoice.payment_secret()); @@ -95,13 +126,24 @@ public async Task RequestPayment(LightMoney amount, TimeSpan e Preimage = Convert.ToHexString(preimage!).ToLower(), Status = LightningPaymentStatus.Pending, Timestamp = DateTimeOffset.FromUnixTimeSeconds(epoch), - PaymentRequests = [bolt11] + PaymentRequests = [bolt11], + AdditionalData = new Dictionary() + { + [LightningPaymentDescriptionKey] = JsonSerializer.SerializeToDocument(descriptionHash.ToString()), + [LightningPaymentExpiryKey] = JsonSerializer.SerializeToDocument(invoice.expires_at()) + } }; + if (lsp is null) + { + + } + + lsp?.WrapInvoice(lp); await Payment(lp); return lp; } - + public async Task PayInvoice(BOLT11PaymentRequest paymentRequest, LightMoney? explicitAmount = null) { @@ -269,10 +311,12 @@ public async Task Handle(Event.Event_PaymentClaimable eventPaymentClaimable) payment.PaymentHash == Convert.ToHexString(eventPaymentClaimable.payment_hash).ToLower() && payment.Inbound && payment.Status == LightningPaymentStatus.Pending); + + var preimage = eventPaymentClaimable.purpose.GetPreimage(out _) ?? (accept?.Preimage is not null ? Convert.FromHexString(accept.Preimage) : null); - if (accept is not null && preimage is not null) + if (accept is not null && preimage is not null && accept.Value == eventPaymentClaimable.amount_msat) _channelManager.claim_funds(preimage); else diff --git a/BTCPayApp.Core/LSP/JIT/BTCPayJIT.cs b/BTCPayApp.Core/LSP/JIT/BTCPayJIT.cs index a6cb50a..39898b5 100644 --- a/BTCPayApp.Core/LSP/JIT/BTCPayJIT.cs +++ b/BTCPayApp.Core/LSP/JIT/BTCPayJIT.cs @@ -3,23 +3,19 @@ namespace BTCPayApp.Core.LSP.JIT; -public class BTCPayJIT: IJITService +using System.Diagnostics; +using BTCPayServer.Lightning; + +public class BTCPayJIT : IJITService { public BTCPayJIT(BTCPayConnectionManager btcPayConnectionManager) { - } public string ProviderName => "BTCPayServer"; - public async Task WrapInvoice(BOLT11PaymentRequest invoice) + public async Task WrapInvoice(LightningPayment lightningPayment) { throw new NotImplementedException(); } -} - -public interface IJITService -{ - public string ProviderName { get; } - public Task WrapInvoice(BOLT11PaymentRequest invoice); } \ No newline at end of file diff --git a/BTCPayApp.Core/LSP/JIT/Flow2Client.cs b/BTCPayApp.Core/LSP/JIT/Flow2Client.cs new file mode 100644 index 0000000..642e71e --- /dev/null +++ b/BTCPayApp.Core/LSP/JIT/Flow2Client.cs @@ -0,0 +1,275 @@ +using System.Net; +using System.Text.Json; +using AngleSharp.Dom; +using BTCPayApp.Core.Attempt2; +using BTCPayApp.Core.Data; +using BTCPayApp.Core.Helpers; +using BTCPayApp.Core.LDK; +using BTCPayServer.Lightning; +using NBitcoin; +using Newtonsoft.Json; +using org.ldk.structs; +using org.ldk.util; +using LightningPayment = BTCPayApp.CommonServer.Models.LightningPayment; + +namespace BTCPayApp.Core.LSP.JIT; + +public class FlowInvoiceDetails() +{ + + public string FeeId { get; set; } + public long FeeAmount { get; set; } + +} + +/// +/// https://docs.voltage.cloud/flow/flow-2.0 +/// +public class VoltageFlow2Jit : IJITService, IScopedHostedService, ILDKEventHandler + +{ + private readonly HttpClient _httpClient; + private readonly Network _network; + private readonly LDKNode _node; + private readonly ChannelManager _channelManager; + + public static Uri? BaseAddress(Network network) + { + return network switch + { + not null when network == Network.Main => new Uri("https://lsp.voltageapi.com"), + not null when network == Network.TestNet => new Uri("https://testnet-lsp.voltageapi.com"), + // not null when network == Network.RegTest => new Uri("https://localhost:5001/jit-lsp"), + _ => null + }; + } + + public VoltageFlow2Jit(IHttpClientFactory httpClientFactory, Network network, LDKNode node, + ChannelManager channelManager) + { + var httpClientInstance = httpClientFactory.CreateClient("VoltageFlow2JIT"); + httpClientInstance.BaseAddress = BaseAddress(network); + + _httpClient = httpClientInstance; + _network = network; + _node = node; + _channelManager = channelManager; + } + + public VoltageFlow2Jit(HttpClient httpClient, Network network) + { + if (httpClient.BaseAddress == null) + throw new ArgumentException( + "HttpClient must have a base address, use Flow2Client.BaseAddress to get a predefined URI", + nameof(httpClient)); + + _httpClient = httpClient; + _network = network; + } + + public async Task GetInfo(CancellationToken cancellationToken = default) + { + var path = "/api/v1/info"; + var response = await _httpClient.GetAsync(path, cancellationToken); + response.EnsureSuccessStatusCode(); + var content = await response.Content.ReadAsStringAsync(cancellationToken); + return JsonConvert.DeserializeObject(content); + } + + public async Task GetFee(LightMoney amount, PubKey pubkey, + CancellationToken cancellationToken = default) + { + var path = "/api/v1/fee"; + var request = new FlowFeeRequest(amount, pubkey); + var response = await _httpClient.PostAsync(path, new StringContent(JsonConvert.SerializeObject(request)), + cancellationToken); + response.EnsureSuccessStatusCode(); + var content = await response.Content.ReadAsStringAsync(cancellationToken); + return JsonConvert.DeserializeObject(content); + } + + public async Task GetProposal(BOLT11PaymentRequest bolt11PaymentRequest, + EndPoint? endPoint = null, string? feeId = null, CancellationToken cancellationToken = default) + { + var path = "/api/v1/proposal"; + var request = new FlowProposalRequest() + { + Bolt11 = bolt11PaymentRequest.ToString(), + Host = endPoint?.Host(), + Port = endPoint?.Port(), + FeeId = feeId, + }; + var response = await _httpClient + .PostAsync(path, new StringContent(JsonConvert.SerializeObject(request)), cancellationToken); + response.EnsureSuccessStatusCode(); + var content = await response.Content.ReadAsStringAsync(cancellationToken); + var result = JsonConvert.DeserializeObject(content); + + return BOLT11PaymentRequest.Parse(result!.WrappedBolt11, _network); + } + + public string ProviderName => "Voltage"; + public async Task<(LightMoney invoiceAmount, LightMoney fee)> CalculateInvoiceAmount(LightMoney expectedAmount) + { + var fee = await GetFee(expectedAmount, _node.NodeId); + return (LightMoney.MilliSatoshis(expectedAmount.MilliSatoshi-fee.Amount), LightMoney.MilliSatoshis(fee.Amount)); + } + + public async Task WrapInvoice(LightningPayment lightningPayment) + { + + if (lightningPayment.PaymentRequests.Count > 1) + { + return; + } + if(lightningPayment.AdditionalData?.TryGetValue("flowlsp", out var lsp) is true && lsp.RootElement.Deserialize() is { } invoiceDetails) + return; + + var lm = new LightMoney(lightningPayment.Value); + var fee = await GetFee(lm, _node.NodeId); + + + if (lm < fee.Amount) + throw new InvalidOperationException("Invoice amount is too low to use Voltage LSP"); + + + return await GetProposal(invoice, null, fee.Id); + } + + public async Task StartAsync(CancellationToken cancellationToken) + { + _node.ConfigUpdated += ConfigUpdated; + _ = Task.Run(async () => + { + + await ConfigUpdated(this, await _node.GetConfig()); + }, cancellationToken); + } + + private FlowInfoResponse? _info; + + private async Task ConfigUpdated(object? sender, LightningConfig e) + { + if (e.JITLSP == ProviderName) + { + _info ??= await GetInfo(); + + + var ni = _info.ToNodeInfo(); + var configPeers = await _node.GetConfig(); + var pubkey = new PubKey(_info.PubKey); + if (configPeers.Peers.TryGetValue(_info.PubKey, out var peer)) + { + //check if the endpoint matches any of the info ones + if(!_info.ConnectionMethods.Any(a => a.ToEndpoint().ToEndpointString().Equals(peer.Endpoint, StringComparison.OrdinalIgnoreCase))) + { + peer = new PeerInfo {Endpoint = _info.ConnectionMethods.First().ToEndpoint().ToEndpointString(), Persistent = true, Trusted = true}; + }else if (peer is {Persistent: true, Trusted: true}) + return; + else + { + peer = peer with + { + Persistent = true, + Trusted = true + }; + } + } + else + { + + peer = new PeerInfo {Endpoint = _info.ConnectionMethods.First().ToEndpoint().ToEndpointString(), Persistent = true, Trusted = true}; + } + + _ = _node.Peer(pubkey, peer); + } + } + + public async Task StopAsync(CancellationToken cancellationToken) + { + _node.ConfigUpdated -= ConfigUpdated; + } + + public async Task Handle(Event.Event_ChannelPending @event) + { + var nodeId = new PubKey(@event.counterparty_node_id); + if(nodeId.ToString() == _info?.PubKey) + { + var channel = _channelManager + .list_channels_with_counterparty(@event.counterparty_node_id) + .FirstOrDefault(a => a.get_channel_id().eq(@event.channel_id)); + if(channel is null) + return; + var channelConfig = channel.get_config(); + channelConfig.set_accept_underpaying_htlcs(true); + _channelManager.update_channel_config(@event.counterparty_node_id, new[] {@event.channel_id}, channelConfig); + } + } + + private bool VerifyInvoice(BOLT11PaymentRequest ourInvoice, + BOLT11PaymentRequest lspInvoice, + LightMoney fee) + { + if(ourInvoice.PaymentHash != lspInvoice.PaymentHash) + return false; + + var expected_lsp_invoice_amt = our_invoice_amt + lsp_fee_msats; + + if (Bolt11Invoice.from_str(ourInvoice.ToString()) is Result_Bolt11InvoiceParseOrSemanticErrorZ.Result_Bolt11InvoiceParseOrSemanticErrorZ_OK + ourInvoiceResult && + Bolt11Invoice.from_str(lspInvoice.ToString()) is Result_Bolt11InvoiceParseOrSemanticErrorZ.Result_Bolt11InvoiceParseOrSemanticErrorZ_OK lspInvoiceResult) + { + ourInvoiceResult.res. + + } + + return false; + + if lsp_invoice.network() != our_invoice.network() { + return Some(format!( + "Received invoice on wrong network: {} != {}", + lsp_invoice.network(), + our_invoice.network() + )); + } + + if lsp_invoice.payment_hash() != our_invoice.payment_hash() { + return Some(format!( + "Received invoice with wrong payment hash: {} != {}", + lsp_invoice.payment_hash(), + our_invoice.payment_hash() + )); + } + + let invoice_pubkey = lsp_invoice.recover_payee_pub_key(); + if invoice_pubkey != self.pubkey { + return Some(format!( + "Received invoice from wrong node: {invoice_pubkey} != {}", + self.pubkey + )); + } + + if lsp_invoice.amount_milli_satoshis().is_none() { + return Some("Invoice amount is missing".to_string()); + } + + if our_invoice.amount_milli_satoshis().is_none() { + return Some("Invoice amount is missing".to_string()); + } + + let lsp_invoice_amt = lsp_invoice.amount_milli_satoshis().expect("just checked"); + let our_invoice_amt = our_invoice.amount_milli_satoshis().expect("just checked"); + + let expected_lsp_invoice_amt = our_invoice_amt + lsp_fee_msats; + + // verify invoice within 10 sats of our target + if lsp_invoice_amt.abs_diff(expected_lsp_invoice_amt) > 10_000 { + return Some(format!( + "Received invoice with wrong amount: {lsp_invoice_amt} when amount was {expected_lsp_invoice_amt}", + )); + } + + None + } + +} \ No newline at end of file diff --git a/BTCPayApp.Core/LSP/JIT/FlowFeeRequest.cs b/BTCPayApp.Core/LSP/JIT/FlowFeeRequest.cs new file mode 100644 index 0000000..da738b8 --- /dev/null +++ b/BTCPayApp.Core/LSP/JIT/FlowFeeRequest.cs @@ -0,0 +1,21 @@ +using BTCPayServer.Lightning; +using NBitcoin; +using Newtonsoft.Json; + +namespace BTCPayApp.Core.LSP.JIT; + +public class FlowFeeRequest +{ + public FlowFeeRequest() + { + } + + public FlowFeeRequest(LightMoney amount, PubKey pubkey) + { + Amount = amount.MilliSatoshi; + PubKey = pubkey.ToHex(); + } + + [JsonProperty("amount_msat")] public long Amount { get; set; } + [JsonProperty("pubkey")] public string PubKey { get; set; } +} \ No newline at end of file diff --git a/BTCPayApp.Core/LSP/JIT/FlowFeeResponse.cs b/BTCPayApp.Core/LSP/JIT/FlowFeeResponse.cs new file mode 100644 index 0000000..868c69e --- /dev/null +++ b/BTCPayApp.Core/LSP/JIT/FlowFeeResponse.cs @@ -0,0 +1,9 @@ +using Newtonsoft.Json; + +namespace BTCPayApp.Core.LSP.JIT; + +public class FlowFeeResponse +{ + [JsonProperty("amount_msat")] public long Amount { get; set; } + [JsonProperty("id")] public required string Id { get; set; } +} \ No newline at end of file diff --git a/BTCPayApp.Core/LSP/JIT/FlowInfoResponse.cs b/BTCPayApp.Core/LSP/JIT/FlowInfoResponse.cs new file mode 100644 index 0000000..866f58a --- /dev/null +++ b/BTCPayApp.Core/LSP/JIT/FlowInfoResponse.cs @@ -0,0 +1,31 @@ +using System.Net; +using BTCPayApp.Core.Helpers; +using BTCPayServer.Lightning; +using NBitcoin; +using Newtonsoft.Json; + +namespace BTCPayApp.Core.LSP.JIT; + +public class FlowInfoResponse +{ + [JsonProperty("connection_methods")] public ConnectionMethod[] ConnectionMethods { get; set; } + [JsonProperty("pubkey")] public required string PubKey { get; set; } + + public NodeInfo[] ToNodeInfo() + { + var pubkey = new PubKey(PubKey); + return ConnectionMethods.Select(method => new NodeInfo(pubkey, method.Address, method.Port)).ToArray(); + } + + public class ConnectionMethod + { + [JsonProperty("address")] public string Address { get; set; } + [JsonProperty("port")] public int Port { get; set; } + [JsonProperty("type")] public string Type { get; set; } + + public EndPoint? ToEndpoint() + { + return EndPointParser.TryParse($"{Address}:{Port}", 9735, out var endpoint) ? endpoint : null; + } + } +} \ No newline at end of file diff --git a/BTCPayApp.Core/LSP/JIT/FlowProposalRequest.cs b/BTCPayApp.Core/LSP/JIT/FlowProposalRequest.cs new file mode 100644 index 0000000..c6ce79f --- /dev/null +++ b/BTCPayApp.Core/LSP/JIT/FlowProposalRequest.cs @@ -0,0 +1,18 @@ +using Newtonsoft.Json; + +namespace BTCPayApp.Core.LSP.JIT; + +public class FlowProposalRequest +{ + [JsonProperty("bolt11")] public required string Bolt11 { get; set; } + + [JsonProperty("host", NullValueHandling = NullValueHandling.Ignore)] + public string? Host { get; set; } + + + [JsonProperty("port", NullValueHandling = NullValueHandling.Ignore)] + public int? Port { get; set; } + + [JsonProperty("fee_id", NullValueHandling = NullValueHandling.Ignore)] + public string? FeeId { get; set; } +} \ No newline at end of file diff --git a/BTCPayApp.Core/LSP/JIT/FlowProposalResponse.cs b/BTCPayApp.Core/LSP/JIT/FlowProposalResponse.cs new file mode 100644 index 0000000..abce8e9 --- /dev/null +++ b/BTCPayApp.Core/LSP/JIT/FlowProposalResponse.cs @@ -0,0 +1,8 @@ +using Newtonsoft.Json; + +namespace BTCPayApp.Core.LSP.JIT; + +public class FlowProposalResponse +{ + [JsonProperty("jit_bolt11")] public required string WrappedBolt11 { get; set; } +} \ No newline at end of file diff --git a/BTCPayApp.Core/LSP/JIT/IJITService.cs b/BTCPayApp.Core/LSP/JIT/IJITService.cs new file mode 100644 index 0000000..dafe715 --- /dev/null +++ b/BTCPayApp.Core/LSP/JIT/IJITService.cs @@ -0,0 +1,11 @@ +using BTCPayServer.Lightning; +using LightningPayment = BTCPayApp.CommonServer.Models.LightningPayment; + +namespace BTCPayApp.Core.LSP.JIT; + +public interface IJITService +{ + public string ProviderName { get; } + public Task<(LightMoney invoiceAmount, LightMoney fee)> CalculateInvoiceAmount(LightMoney expectedAmount); + public Task WrapInvoice(LightningPayment lightningPayment); +} \ No newline at end of file From 86c699d82385e53e35bfb01615f820a7cbb49c35 Mon Sep 17 00:00:00 2001 From: Kukks Date: Wed, 26 Jun 2024 13:29:52 +0200 Subject: [PATCH 02/14] crying my way to success --- BTCPayApp.Core/Data/AppDbContext.cs | 12 +- BTCPayApp.Core/LDK/LDKExtensions.cs | 1 + .../LDK/LDKOpenChannelRequestEventHandler.cs | 5 + BTCPayApp.Core/LDK/PaymentsManager.cs | 189 +++++++++++------- BTCPayApp.Core/LSP/JIT/Flow2Client.cs | 122 ++++------- BTCPayApp.Core/LSP/JIT/IJITService.cs | 61 +++++- 6 files changed, 226 insertions(+), 164 deletions(-) diff --git a/BTCPayApp.Core/Data/AppDbContext.cs b/BTCPayApp.Core/Data/AppDbContext.cs index d235951..9572ea0 100644 --- a/BTCPayApp.Core/Data/AppDbContext.cs +++ b/BTCPayApp.Core/Data/AppDbContext.cs @@ -32,9 +32,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } } -public class SpendableCoin -{ - public string Script { get; set; } - [Key] public string Outpoint { get; set; } - public byte[] Data { get; set; } -} \ No newline at end of file +// public class SpendableCoin +// { +// public string Script { get; set; } +// [Key] public string Outpoint { get; set; } +// public byte[] Data { get; set; } +// } \ No newline at end of file diff --git a/BTCPayApp.Core/LDK/LDKExtensions.cs b/BTCPayApp.Core/LDK/LDKExtensions.cs index 06d880f..2ae72c8 100644 --- a/BTCPayApp.Core/LDK/LDKExtensions.cs +++ b/BTCPayApp.Core/LDK/LDKExtensions.cs @@ -214,6 +214,7 @@ public static IServiceCollection AddLDK(this IServiceCollection services) // services.AddScoped(provider => // provider.GetRequiredService()); services.AddScoped(provider => provider.GetRequiredService()); + services.AddScoped(provider => provider.GetRequiredService()); services.AddScoped(provider => provider.GetRequiredService()); services.AddScoped(provider => provider.GetRequiredService()); services.AddScoped(provider => provider.GetRequiredService()); diff --git a/BTCPayApp.Core/LDK/LDKOpenChannelRequestEventHandler.cs b/BTCPayApp.Core/LDK/LDKOpenChannelRequestEventHandler.cs index efad161..4353cae 100644 --- a/BTCPayApp.Core/LDK/LDKOpenChannelRequestEventHandler.cs +++ b/BTCPayApp.Core/LDK/LDKOpenChannelRequestEventHandler.cs @@ -1,4 +1,5 @@ using BTCPayApp.Core.Attempt2; +using BTCPayApp.Core.Helpers; using org.ldk.structs; using UInt128 = org.ldk.util.UInt128; @@ -33,6 +34,7 @@ public async Task Handle(Event.Event_OpenChannelRequest eventOpenChannelRequest) eventOpenChannelRequest.counterparty_node_id, userChannelId ); + AcceptedChannel?.Invoke(this, eventOpenChannelRequest); return; } } @@ -43,8 +45,11 @@ public async Task Handle(Event.Event_OpenChannelRequest eventOpenChannelRequest) eventOpenChannelRequest.counterparty_node_id, userChannelId); + AcceptedChannel?.Invoke(this, eventOpenChannelRequest); //TODO: if we want to reject the channel, we can call reject_channel //_channelManager.force_close_without_broadcasting_txn(eventOpenChannelRequest.temporary_channel_id, eventOpenChannelRequest.counterparty_node_id); } + + public AsyncEventHandler? AcceptedChannel; } \ No newline at end of file diff --git a/BTCPayApp.Core/LDK/PaymentsManager.cs b/BTCPayApp.Core/LDK/PaymentsManager.cs index f7689ef..168e47d 100644 --- a/BTCPayApp.Core/LDK/PaymentsManager.cs +++ b/BTCPayApp.Core/LDK/PaymentsManager.cs @@ -1,4 +1,5 @@ -using System.Security.Cryptography; +using System.Collections.Concurrent; +using System.Security.Cryptography; using System.Text.Json; using BTCPayApp.Core.Attempt2; using BTCPayApp.Core.Data; @@ -15,6 +16,7 @@ namespace BTCPayApp.Core.LDK; public class PaymentsManager : + IScopedHostedService, ILDKEventHandler, ILDKEventHandler, ILDKEventHandler, @@ -26,6 +28,7 @@ public class PaymentsManager : private readonly IDbContextFactory _dbContextFactory; private readonly ChannelManager _channelManager; + private readonly LDKOpenChannelRequestEventHandler _openChannelRequestEventHandler; private readonly Logger _logger; private readonly NodeSigner _nodeSigner; private readonly Network _network; @@ -34,6 +37,7 @@ public class PaymentsManager : public PaymentsManager( IDbContextFactory dbContextFactory, ChannelManager channelManager, + LDKOpenChannelRequestEventHandler openChannelRequestEventHandler, Logger logger, NodeSigner nodeSigner, Network network, @@ -41,6 +45,7 @@ public PaymentsManager( { _dbContextFactory = dbContextFactory; _channelManager = channelManager; + _openChannelRequestEventHandler = openChannelRequestEventHandler; _logger = logger; _nodeSigner = nodeSigner; _network = network; @@ -56,89 +61,102 @@ public async Task> List( .ToListAsync(cancellationToken: cancellationToken))!; } - private Bolt11Invoice CreateInvoice(long? amt, int expirySeconds, byte[] descHash) - { - var keyMaterial = _nodeSigner.get_inbound_payment_key_material(); - var preimage = RandomUtils.GetBytes(32); - var paymentHash = SHA256.HashData(preimage); - var expandedKey = ExpandedKey.of(keyMaterial); - var inboundPayment = _channelManager.create_inbound_payment_for_hash(paymentHash, - amt is null ? Option_u64Z.none() : Option_u64Z.some(amt.Value), expirySeconds, Option_u16Z.none())); - var paymentSecret = inboundPayment is Result_ThirtyTwoBytesNoneZ.Result_ThirtyTwoBytesNoneZ_OK ok - ? ok.res - : throw new Exception("Error creating inbound payment"); - - _nodeSigner. - var invoice = Bolt11Invoice.from_signed(_channelManager, _nodeSigner, _logger, _network.GetLdkCurrency(), - - - } + // private Bolt11Invoice CreateInvoice(long? amt, int expirySeconds, byte[] descHash) + // { + // var keyMaterial = _nodeSigner.get_inbound_payment_key_material(); + // var preimage = RandomUtils.GetBytes(32); + // var paymentHash = SHA256.HashData(preimage); + // var expandedKey = ExpandedKey.of(keyMaterial); + // var inboundPayment = _channelManager.create_inbound_payment_for_hash(paymentHash, + // amt is null ? Option_u64Z.none() : Option_u64Z.some(amt.Value), expirySeconds, Option_u16Z.none())); + // var paymentSecret = inboundPayment is Result_ThirtyTwoBytesNoneZ.Result_ThirtyTwoBytesNoneZ_OK ok + // ? ok.res + // : throw new Exception("Error creating inbound payment"); + // + // _nodeSigner. + // var invoice = Bolt11Invoice.from_signed(_channelManager, _nodeSigner, _logger, _network.GetLdkCurrency(), + // } public async Task RequestPayment(LightMoney amount, TimeSpan expiry, uint256 descriptionHash) { var amt = amount == LightMoney.Zero ? Option_u64Z.none() : Option_u64Z.some(amount.MilliSatoshi); - - var epoch = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); - // var result = - // org.ldk.util.UtilMethods.create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_hash( - // _channelManager, _nodeSigner, _logger, - // _network.GetLdkCurrency(), amt, description, epoch, (int) Math.Ceiling(expiry.TotalSeconds), - // paymentHash, Option_u16Z.none()); + + var now = DateTimeOffset.UtcNow; + var epoch = now.ToUnixTimeSeconds(); var descHashBytes = Sha256.from_bytes(descriptionHash.ToBytes()); var lsp = await _ldkNode.GetJITLSPService(); - var result = await Task.Run(() => - org.ldk.util.UtilMethods.create_invoice_from_channelmanager_with_description_hash_and_duration_since_epoch( - _channelManager, _nodeSigner, _logger, - _network.GetLdkCurrency(), amt, descHashBytes, epoch, (int) Math.Ceiling(expiry.TotalSeconds), - Option_u16Z.none())); - if (result is Result_Bolt11InvoiceSignOrCreationErrorZ.Result_Bolt11InvoiceSignOrCreationErrorZ_Err err) - { - throw new Exception(err.err.to_str()); - } - - var keyMaterial = _nodeSigner.get_inbound_payment_key_material(); - var expandedKey = ExpandedKey.of(keyMaterial); - _channelManager.create_inbound_payment_for_hash() - UtilMethods.create_invoice_from_channelmanager() - - var invoice = ((Result_Bolt11InvoiceSignOrCreationErrorZ.Result_Bolt11InvoiceSignOrCreationErrorZ_OK) result) - .res; - var preimageResult = _channelManager.get_payment_preimage(invoice.payment_hash(), invoice.payment_secret()); - byte[] preimage = null; - if (preimageResult is - Result_ThirtyTwoBytesAPIErrorZ.Result_ThirtyTwoBytesAPIErrorZ_Err errx) - { - - throw new Exception(errx.err.GetError()); - }else if (preimageResult is Result_ThirtyTwoBytesAPIErrorZ.Result_ThirtyTwoBytesAPIErrorZ_OK ok) - { - preimage = ok.res; - } - var bolt11 = invoice.to_str(); - var lp = new LightningPayment() + + generateInvoice: + JITFeeResponse? jitFeeReponse = null; + if (lsp is not null) + { + jitFeeReponse = await lsp.CalculateInvoiceAmount(amount); + if (jitFeeReponse is not null) + { + + amt = Option_u64Z.some(jitFeeReponse.AmountToGenerateOurInvoice); + + } + else + { + lsp = null; + } + } + + var result = await Task.Run(() => + org.ldk.util.UtilMethods.create_invoice_from_channelmanager_with_description_hash_and_duration_since_epoch( + _channelManager, _nodeSigner, _logger, + _network.GetLdkCurrency(), amt, descHashBytes, epoch, (int) Math.Ceiling(expiry.TotalSeconds), + Option_u16Z.none())); + if (result is Result_Bolt11InvoiceSignOrCreationErrorZ.Result_Bolt11InvoiceSignOrCreationErrorZ_Err err) + { + throw new Exception(err.err.to_str()); + } + var originalInvoice = ((Result_Bolt11InvoiceSignOrCreationErrorZ.Result_Bolt11InvoiceSignOrCreationErrorZ_OK) result) + .res; + + + var preimageResult = _channelManager.get_payment_preimage(originalInvoice.payment_hash(), originalInvoice.payment_secret()); + var preimage = preimageResult switch + { + Result_ThirtyTwoBytesAPIErrorZ.Result_ThirtyTwoBytesAPIErrorZ_Err errx => throw new Exception( + errx.err.GetError()), + Result_ThirtyTwoBytesAPIErrorZ.Result_ThirtyTwoBytesAPIErrorZ_OK ok => ok.res, + _ => throw new Exception("Unknown error retrieving preimage") + }; + + + var parsedOriginalInvoice= BOLT11PaymentRequest.Parse(originalInvoice.to_str(), _network); + var lp = new LightningPayment() { Inbound = true, PaymentId = "default", Value = amount.MilliSatoshi, - PaymentHash = Convert.ToHexString(invoice.payment_hash()).ToLower(), - Secret = Convert.ToHexString(invoice.payment_secret()).ToLower(), + PaymentHash = parsedOriginalInvoice.PaymentHash!.ToString(), + Secret = parsedOriginalInvoice.PaymentSecret!.ToString(), Preimage = Convert.ToHexString(preimage!).ToLower(), Status = LightningPaymentStatus.Pending, - Timestamp = DateTimeOffset.FromUnixTimeSeconds(epoch), - PaymentRequests = [bolt11], + Timestamp = now, + PaymentRequests = [parsedOriginalInvoice.ToString()], AdditionalData = new Dictionary() { [LightningPaymentDescriptionKey] = JsonSerializer.SerializeToDocument(descriptionHash.ToString()), - [LightningPaymentExpiryKey] = JsonSerializer.SerializeToDocument(invoice.expires_at()) + [LightningPaymentExpiryKey] = JsonSerializer.SerializeToDocument(originalInvoice.expires_at()) } }; - if (lsp is null) + + if (lsp is not null) { - + if(!await lsp.WrapInvoice(lp,jitFeeReponse )) + { + + amt = amount == LightMoney.Zero ? Option_u64Z.none() : Option_u64Z.some(amount.MilliSatoshi); + lsp = null; + goto generateInvoice; + } } - - lsp?.WrapInvoice(lp); + await Payment(lp); return lp; @@ -311,16 +329,32 @@ public async Task Handle(Event.Event_PaymentClaimable eventPaymentClaimable) payment.PaymentHash == Convert.ToHexString(eventPaymentClaimable.payment_hash).ToLower() && payment.Inbound && payment.Status == LightningPaymentStatus.Pending); - - + var preimage = eventPaymentClaimable.purpose.GetPreimage(out _) ?? (accept?.Preimage is not null ? Convert.FromHexString(accept.Preimage) : null); - if (accept is not null && preimage is not null && accept.Value == eventPaymentClaimable.amount_msat) - _channelManager.claim_funds(preimage); - else + + if (accept is null || preimage is null) + { _channelManager.fail_htlc_backwards(eventPaymentClaimable.payment_hash); + return; + } + + if (accept.Value == eventPaymentClaimable.amount_msat) + { + + _channelManager.claim_funds(preimage); + return; + } + //this discrepancy could have been used to pay for a JIT channel opening + else if(_acceptedChannels.TryGetValue(eventPaymentClaimable.via_channel_id.hash(), out var channelRequest)) + { + + } + + + _channelManager.fail_htlc_backwards(eventPaymentClaimable.payment_hash); } public async Task Handle(Event.Event_PaymentClaimed eventPaymentClaimed) @@ -343,4 +377,21 @@ await PaymentUpdate(Convert.ToHexString(eventPaymentSent.payment_hash).ToLower() ((Option_ThirtyTwoBytesZ.Option_ThirtyTwoBytesZ_Some) eventPaymentSent.payment_id).some).ToLower(), false, Convert.ToHexString(eventPaymentSent.payment_preimage).ToLower()); } + + public async Task StartAsync(CancellationToken cancellationToken) + { + _openChannelRequestEventHandler.AcceptedChannel += AcceptedChannel; + } + + private ConcurrentDictionary _acceptedChannels = new(); + private Task AcceptedChannel(object? sender, Event.Event_OpenChannelRequest e) + { + _acceptedChannels.TryAdd(e.temporary_channel_id.hash(), e); + return Task.CompletedTask; + } + + public async Task StopAsync(CancellationToken cancellationToken) + { + _openChannelRequestEventHandler.AcceptedChannel -= AcceptedChannel; + } } \ No newline at end of file diff --git a/BTCPayApp.Core/LSP/JIT/Flow2Client.cs b/BTCPayApp.Core/LSP/JIT/Flow2Client.cs index 642e71e..f75d9d8 100644 --- a/BTCPayApp.Core/LSP/JIT/Flow2Client.cs +++ b/BTCPayApp.Core/LSP/JIT/Flow2Client.cs @@ -6,21 +6,16 @@ using BTCPayApp.Core.Helpers; using BTCPayApp.Core.LDK; using BTCPayServer.Lightning; +using Microsoft.Extensions.Logging; using NBitcoin; using Newtonsoft.Json; using org.ldk.structs; using org.ldk.util; +using JsonSerializer = System.Text.Json.JsonSerializer; using LightningPayment = BTCPayApp.CommonServer.Models.LightningPayment; namespace BTCPayApp.Core.LSP.JIT; -public class FlowInvoiceDetails() -{ - - public string FeeId { get; set; } - public long FeeAmount { get; set; } - -} /// /// https://docs.voltage.cloud/flow/flow-2.0 @@ -32,6 +27,7 @@ public class VoltageFlow2Jit : IJITService, IScopedHostedService, ILDKEventHandl private readonly Network _network; private readonly LDKNode _node; private readonly ChannelManager _channelManager; + private readonly ILogger _logger; public static Uri? BaseAddress(Network network) { @@ -45,7 +41,7 @@ public class VoltageFlow2Jit : IJITService, IScopedHostedService, ILDKEventHandl } public VoltageFlow2Jit(IHttpClientFactory httpClientFactory, Network network, LDKNode node, - ChannelManager channelManager) + ChannelManager channelManager, ILogger logger) { var httpClientInstance = httpClientFactory.CreateClient("VoltageFlow2JIT"); httpClientInstance.BaseAddress = BaseAddress(network); @@ -54,6 +50,7 @@ public VoltageFlow2Jit(IHttpClientFactory httpClientFactory, Network network, LD _network = network; _node = node; _channelManager = channelManager; + _logger = logger; } public VoltageFlow2Jit(HttpClient httpClient, Network network) @@ -109,31 +106,50 @@ public async Task GetProposal(BOLT11PaymentRequest bolt11P } public string ProviderName => "Voltage"; - public async Task<(LightMoney invoiceAmount, LightMoney fee)> CalculateInvoiceAmount(LightMoney expectedAmount) + public async Task CalculateInvoiceAmount(LightMoney expectedAmount) { - var fee = await GetFee(expectedAmount, _node.NodeId); - return (LightMoney.MilliSatoshis(expectedAmount.MilliSatoshi-fee.Amount), LightMoney.MilliSatoshis(fee.Amount)); + try + { + + var fee = await GetFee(expectedAmount, _node.NodeId); + return new JITFeeResponse(expectedAmount, expectedAmount + fee.Amount, fee.Amount, fee.Id, ProviderName); + } + catch (Exception e) + { + _logger.LogError(e, "Error while calculating invoice amount"); + return null; + } } - public async Task WrapInvoice(LightningPayment lightningPayment) + public const string LightningPaymentJITFeeKey = "JITFeeKey"; + public const string LightningPaymentLSPKey = "LSP"; + public async Task WrapInvoice(LightningPayment lightningPayment, JITFeeResponse? fee = null) { if (lightningPayment.PaymentRequests.Count > 1) { - return; + return false; } - if(lightningPayment.AdditionalData?.TryGetValue("flowlsp", out var lsp) is true && lsp.RootElement.Deserialize() is { } invoiceDetails) - return; + if(lightningPayment.AdditionalData?.ContainsKey(LightningPaymentLSPKey) is true) + return false; - var lm = new LightMoney(lightningPayment.Value); - var fee = await GetFee(lm, _node.NodeId); + fee??= await CalculateInvoiceAmount(new LightMoney(lightningPayment.Value)); + + if(fee is null) + return false; + var invoice = BOLT11PaymentRequest.Parse(lightningPayment.PaymentRequests[0], _network); - if (lm < fee.Amount) - throw new InvalidOperationException("Invoice amount is too low to use Voltage LSP"); + var proposal = await GetProposal(invoice,null, fee!.FeeIdentifier); + if(proposal.MinimumAmount != fee.AmountToRequestPayer || proposal.PaymentHash != invoice.PaymentHash) + return false; - return await GetProposal(invoice, null, fee.Id); + lightningPayment.PaymentRequests.Insert(0, proposal.ToString()); + lightningPayment.AdditionalData ??= new Dictionary(); + lightningPayment.AdditionalData[LightningPaymentLSPKey] = JsonSerializer.SerializeToDocument(ProviderName); + lightningPayment.AdditionalData[LightningPaymentJITFeeKey] = JsonSerializer.SerializeToDocument(fee); + return true; } public async Task StartAsync(CancellationToken cancellationToken) @@ -205,71 +221,5 @@ public async Task Handle(Event.Event_ChannelPending @event) _channelManager.update_channel_config(@event.counterparty_node_id, new[] {@event.channel_id}, channelConfig); } } - - private bool VerifyInvoice(BOLT11PaymentRequest ourInvoice, - BOLT11PaymentRequest lspInvoice, - LightMoney fee) - { - if(ourInvoice.PaymentHash != lspInvoice.PaymentHash) - return false; - - var expected_lsp_invoice_amt = our_invoice_amt + lsp_fee_msats; - - if (Bolt11Invoice.from_str(ourInvoice.ToString()) is Result_Bolt11InvoiceParseOrSemanticErrorZ.Result_Bolt11InvoiceParseOrSemanticErrorZ_OK - ourInvoiceResult && - Bolt11Invoice.from_str(lspInvoice.ToString()) is Result_Bolt11InvoiceParseOrSemanticErrorZ.Result_Bolt11InvoiceParseOrSemanticErrorZ_OK lspInvoiceResult) - { - ourInvoiceResult.res. - - } - - return false; - - if lsp_invoice.network() != our_invoice.network() { - return Some(format!( - "Received invoice on wrong network: {} != {}", - lsp_invoice.network(), - our_invoice.network() - )); - } - - if lsp_invoice.payment_hash() != our_invoice.payment_hash() { - return Some(format!( - "Received invoice with wrong payment hash: {} != {}", - lsp_invoice.payment_hash(), - our_invoice.payment_hash() - )); - } - - let invoice_pubkey = lsp_invoice.recover_payee_pub_key(); - if invoice_pubkey != self.pubkey { - return Some(format!( - "Received invoice from wrong node: {invoice_pubkey} != {}", - self.pubkey - )); - } - - if lsp_invoice.amount_milli_satoshis().is_none() { - return Some("Invoice amount is missing".to_string()); - } - - if our_invoice.amount_milli_satoshis().is_none() { - return Some("Invoice amount is missing".to_string()); - } - - let lsp_invoice_amt = lsp_invoice.amount_milli_satoshis().expect("just checked"); - let our_invoice_amt = our_invoice.amount_milli_satoshis().expect("just checked"); - - let expected_lsp_invoice_amt = our_invoice_amt + lsp_fee_msats; - - // verify invoice within 10 sats of our target - if lsp_invoice_amt.abs_diff(expected_lsp_invoice_amt) > 10_000 { - return Some(format!( - "Received invoice with wrong amount: {lsp_invoice_amt} when amount was {expected_lsp_invoice_amt}", - )); - } - - None - } } \ No newline at end of file diff --git a/BTCPayApp.Core/LSP/JIT/IJITService.cs b/BTCPayApp.Core/LSP/JIT/IJITService.cs index dafe715..51620d5 100644 --- a/BTCPayApp.Core/LSP/JIT/IJITService.cs +++ b/BTCPayApp.Core/LSP/JIT/IJITService.cs @@ -1,4 +1,6 @@ -using BTCPayServer.Lightning; +using System.Text.Json; +using System.Text.Json.Serialization; +using BTCPayServer.Lightning; using LightningPayment = BTCPayApp.CommonServer.Models.LightningPayment; namespace BTCPayApp.Core.LSP.JIT; @@ -6,6 +8,59 @@ namespace BTCPayApp.Core.LSP.JIT; public interface IJITService { public string ProviderName { get; } - public Task<(LightMoney invoiceAmount, LightMoney fee)> CalculateInvoiceAmount(LightMoney expectedAmount); - public Task WrapInvoice(LightningPayment lightningPayment); + public Task CalculateInvoiceAmount(LightMoney expectedAmount); + public Task WrapInvoice(LightningPayment lightningPayment, JITFeeResponse? feeReponse); +} + +public class LightMoneyJsonConverter : JsonConverter +{ + public override LightMoney? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return reader.TokenType switch + { + JsonTokenType.String => LightMoney.Parse(reader.GetString()), + JsonTokenType.Null => null, + _ => throw new ArgumentOutOfRangeException() + }; + } + + public override void Write(Utf8JsonWriter writer, LightMoney value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString()); + } +} + +public record JITFeeResponse +{ + public JITFeeResponse(LightMoney AmountToRequestPayer, LightMoney AmountToGenerateOurInvoice, LightMoney LSPFee, + string FeeIdentifier, string LSP) + { + this.AmountToRequestPayer = AmountToRequestPayer; + this.AmountToGenerateOurInvoice = AmountToGenerateOurInvoice; + this.LSPFee = LSPFee; + this.FeeIdentifier = FeeIdentifier; + this.LSP = LSP; + } + + [JsonConverter(typeof(LightMoneyJsonConverter))] + public LightMoney AmountToRequestPayer { get; init; } + + [JsonConverter(typeof(LightMoneyJsonConverter))] + public LightMoney AmountToGenerateOurInvoice { get; init; } + + [JsonConverter(typeof(LightMoneyJsonConverter))] + public LightMoney LSPFee { get; init; } + + public string FeeIdentifier { get; init; } + + public string LSP { get; set; } + + public void Deconstruct(out LightMoney AmountToRequestPayer, out LightMoney AmountToGenerateOurInvoice, + out LightMoney LSPFee, out string FeeIdentifier) + { + AmountToRequestPayer = this.AmountToRequestPayer; + AmountToGenerateOurInvoice = this.AmountToGenerateOurInvoice; + LSPFee = this.LSPFee; + FeeIdentifier = this.FeeIdentifier; + } } \ No newline at end of file From c1788faefe231658e3ab7f157b4dcb749e15d86a Mon Sep 17 00:00:00 2001 From: Kukks Date: Wed, 26 Jun 2024 14:20:20 +0200 Subject: [PATCH 03/14] theoretically functional --- BTCPayApp.Core/Attempt2/LDKNode.cs | 8 +++++-- BTCPayApp.Core/LDK/LDKExtensions.cs | 4 +++- BTCPayApp.Core/LDK/LDKPeerHandler.cs | 4 ++-- BTCPayApp.Core/LDK/PaymentsManager.cs | 18 +++++++++++++-- BTCPayApp.Core/LSP/JIT/BTCPayJIT.cs | 21 ------------------ BTCPayApp.Core/LSP/JIT/Flow2Client.cs | 11 ---------- .../Pages/Settings/LightningPage.razor | 22 ++++++++++++++++++- 7 files changed, 48 insertions(+), 40 deletions(-) delete mode 100644 BTCPayApp.Core/LSP/JIT/BTCPayJIT.cs diff --git a/BTCPayApp.Core/Attempt2/LDKNode.cs b/BTCPayApp.Core/Attempt2/LDKNode.cs index 6059d52..ce14083 100644 --- a/BTCPayApp.Core/Attempt2/LDKNode.cs +++ b/BTCPayApp.Core/Attempt2/LDKNode.cs @@ -194,8 +194,12 @@ public async Task GetConfig() await _configLoaded.Task; return _config!; } - - private async Task UpdateConfig(LightningConfig config) + public async Task GetJITLSPs() + { + return ServiceProvider.GetServices().Select(jit => jit.ProviderName).ToArray(); + } + + public async Task UpdateConfig(LightningConfig config) { await _started.Task; await _configProvider.Set(LightningConfig.Key, config); diff --git a/BTCPayApp.Core/LDK/LDKExtensions.cs b/BTCPayApp.Core/LDK/LDKExtensions.cs index 2ae72c8..f2275d5 100644 --- a/BTCPayApp.Core/LDK/LDKExtensions.cs +++ b/BTCPayApp.Core/LDK/LDKExtensions.cs @@ -303,7 +303,9 @@ public static IServiceCollection AddLDK(this IServiceCollection services) services.AddScoped(provider => provider.GetRequiredService().as_Router()); - services.AddScoped(); + services.AddScoped(); + services.AddScoped(provider => provider.GetRequiredService()); + services.AddScoped(provider => provider.GetRequiredService()); return services; } diff --git a/BTCPayApp.Core/LDK/LDKPeerHandler.cs b/BTCPayApp.Core/LDK/LDKPeerHandler.cs index 8728ba0..742f818 100644 --- a/BTCPayApp.Core/LDK/LDKPeerHandler.cs +++ b/BTCPayApp.Core/LDK/LDKPeerHandler.cs @@ -65,7 +65,7 @@ private async Task BtcPayAppServerClientOnOnServerNodeInfo(object? sender, strin if (config.Peers.ContainsKey(nodeInfo.NodeId.ToString())) return; var endpoint = new IPEndPoint(IPAddress.Parse(nodeInfo.Host), nodeInfo.Port); - await _node.Peer(nodeInfo.NodeId.ToString(), new PeerInfo() + await _node.Peer(nodeInfo.NodeId, new PeerInfo() { Endpoint = endpoint.ToString(), Persistent = true, @@ -224,7 +224,7 @@ await listener.AcceptTcpClientAsync(cancellationToken), if (peer.Endpoint != remote.ToString()) { peer.Endpoint = remote.ToString()!; - await _node.Peer(theirNodeId.ToString(), peer); + await _node.Peer(theirNodeId, peer); } } diff --git a/BTCPayApp.Core/LDK/PaymentsManager.cs b/BTCPayApp.Core/LDK/PaymentsManager.cs index 168e47d..3a8cc45 100644 --- a/BTCPayApp.Core/LDK/PaymentsManager.cs +++ b/BTCPayApp.Core/LDK/PaymentsManager.cs @@ -348,9 +348,23 @@ public async Task Handle(Event.Event_PaymentClaimable eventPaymentClaimable) return; } //this discrepancy could have been used to pay for a JIT channel opening - else if(_acceptedChannels.TryGetValue(eventPaymentClaimable.via_channel_id.hash(), out var channelRequest)) + else if(_acceptedChannels.TryGetValue(eventPaymentClaimable.via_channel_id.hash(), out var channelRequest) && + accept.AdditionalData.TryGetValue(VoltageFlow2Jit.LightningPaymentLSPKey, out var lspDoc ) && + lspDoc.Deserialize() is { } lsp && + await _ldkNode.GetJITLSPService() is { } lspService && + lspService.ProviderName == lsp && + accept.AdditionalData.TryGetValue(VoltageFlow2Jit.LightningPaymentJITFeeKey, out var lspFee ) && lspFee.Deserialize() is { } fee) { - + if (fee.AmountToGenerateOurInvoice == eventPaymentClaimable.amount_msat) + { + _acceptedChannels.Remove(eventPaymentClaimable.via_channel_id.hash(), out _); + _channelManager.claim_funds(preimage); + return; + } + } + else + { + _channelManager.fail_htlc_backwards(eventPaymentClaimable.payment_hash); } diff --git a/BTCPayApp.Core/LSP/JIT/BTCPayJIT.cs b/BTCPayApp.Core/LSP/JIT/BTCPayJIT.cs deleted file mode 100644 index 39898b5..0000000 --- a/BTCPayApp.Core/LSP/JIT/BTCPayJIT.cs +++ /dev/null @@ -1,21 +0,0 @@ -using BTCPayApp.Core.Attempt2; -using BTCPayServer.Lightning; - -namespace BTCPayApp.Core.LSP.JIT; - -using System.Diagnostics; -using BTCPayServer.Lightning; - -public class BTCPayJIT : IJITService -{ - public BTCPayJIT(BTCPayConnectionManager btcPayConnectionManager) - { - } - - public string ProviderName => "BTCPayServer"; - - public async Task WrapInvoice(LightningPayment lightningPayment) - { - throw new NotImplementedException(); - } -} \ No newline at end of file diff --git a/BTCPayApp.Core/LSP/JIT/Flow2Client.cs b/BTCPayApp.Core/LSP/JIT/Flow2Client.cs index f75d9d8..c6ff046 100644 --- a/BTCPayApp.Core/LSP/JIT/Flow2Client.cs +++ b/BTCPayApp.Core/LSP/JIT/Flow2Client.cs @@ -53,17 +53,6 @@ public VoltageFlow2Jit(IHttpClientFactory httpClientFactory, Network network, LD _logger = logger; } - public VoltageFlow2Jit(HttpClient httpClient, Network network) - { - if (httpClient.BaseAddress == null) - throw new ArgumentException( - "HttpClient must have a base address, use Flow2Client.BaseAddress to get a predefined URI", - nameof(httpClient)); - - _httpClient = httpClient; - _network = network; - } - public async Task GetInfo(CancellationToken cancellationToken = default) { var path = "/api/v1/info"; diff --git a/BTCPayApp.UI/Pages/Settings/LightningPage.razor b/BTCPayApp.UI/Pages/Settings/LightningPage.razor index a01e9a9..4e888aa 100644 --- a/BTCPayApp.UI/Pages/Settings/LightningPage.razor +++ b/BTCPayApp.UI/Pages/Settings/LightningPage.razor @@ -42,6 +42,16 @@ +
+ + +
} @if (!string.IsNullOrEmpty(_config?.LightningDerivationPath)) @@ -83,7 +93,7 @@ private string? ConfiguredConnectionString; private string ConnectionString => $"type=app;group={OnChainWalletManager.WalletConfig?.Derivations[WalletDerivation.LightningScripts].Identifier}".ToLower(); - + private string[] JITOptions; protected override async Task OnInitializedAsync() { await base.OnInitializedAsync(); @@ -91,6 +101,7 @@ if (LightningNodeManager?.Node is not null) { _config = await LightningNodeManager.Node.GetConfig(); + JITOptions = await LightningNodeManager.Node.GetJITLSPs(); } var acc = AccountManager.GetAccount(); if (acc?.CurrentStoreId != null) @@ -152,4 +163,13 @@ Logger.LogError(ex, "Error configuring LN wallet"); } } + + private async Task OnSelectLSP(ChangeEventArgs obj) + { + + _config = await LightningNodeManager.Node.GetConfig(); + _config.JITLSP = obj.Value?.ToString(); + await LightningNodeManager.Node.UpdateConfig(_config); + } + } From 995326483676765fa3aa1449f6dd6e93c1b45fe3 Mon Sep 17 00:00:00 2001 From: Kukks Date: Thu, 27 Jun 2024 13:21:56 +0200 Subject: [PATCH 04/14] Refactor connection to isolate json frameworks and start backup --- .../Attempt2/BTCPayAppServerClient.cs | 91 ++++++++++---- .../Attempt2/BTCPayConnectionManager.cs | 6 +- .../Attempt2/BTCPayPaymentsNotifier.cs | 13 +- .../Attempt2/LightningNodeService.cs | 1 - BTCPayApp.Core/BTCPayApp.Core.csproj | 3 +- BTCPayApp.Core/Data/AppDbContext.cs | 71 ++++++++--- BTCPayApp.Core/Data/Setting.cs | 2 +- BTCPayApp.Core/DatabaseConfigProvider.cs | 83 ++++++++++++- .../JsonConverters/UInt256JsonConverter.cs | 116 ++++++++++++++++++ BTCPayApp.Core/LDK/PaymentsManager.cs | 106 ++++++++++------ BTCPayApp.Core/LSP/JIT/Flow2Client.cs | 29 ++--- BTCPayApp.Core/LSP/JIT/IJITService.cs | 26 +--- .../20240502121610_Init.Designer.cs | 88 ------------- .../Migrations/20240508085527_PR.Designer.cs | 92 -------------- .../Migrations/20240508085527_PR.cs | 29 ----- .../20240527105733_upd2.Designer.cs | 92 -------------- .../Migrations/20240527105733_upd2.cs | 54 -------- .../Migrations/20240621120336_adddatatoln.cs | 29 ----- ...ner.cs => 20240627110659_Init.Designer.cs} | 75 +++++------ ...2121610_Init.cs => 20240627110659_Init.cs} | 10 +- .../Migrations/AppDbContextModelSnapshot.cs | 71 +++++------ 21 files changed, 496 insertions(+), 591 deletions(-) create mode 100644 BTCPayApp.Core/JsonConverters/UInt256JsonConverter.cs delete mode 100644 BTCPayApp.Core/Migrations/20240502121610_Init.Designer.cs delete mode 100644 BTCPayApp.Core/Migrations/20240508085527_PR.Designer.cs delete mode 100644 BTCPayApp.Core/Migrations/20240508085527_PR.cs delete mode 100644 BTCPayApp.Core/Migrations/20240527105733_upd2.Designer.cs delete mode 100644 BTCPayApp.Core/Migrations/20240527105733_upd2.cs delete mode 100644 BTCPayApp.Core/Migrations/20240621120336_adddatatoln.cs rename BTCPayApp.Core/Migrations/{20240621120336_adddatatoln.Designer.cs => 20240627110659_Init.Designer.cs} (90%) rename BTCPayApp.Core/Migrations/{20240502121610_Init.cs => 20240627110659_Init.cs} (90%) diff --git a/BTCPayApp.Core/Attempt2/BTCPayAppServerClient.cs b/BTCPayApp.Core/Attempt2/BTCPayAppServerClient.cs index e1c9a1c..b1324a8 100644 --- a/BTCPayApp.Core/Attempt2/BTCPayAppServerClient.cs +++ b/BTCPayApp.Core/Attempt2/BTCPayAppServerClient.cs @@ -8,10 +8,51 @@ using Microsoft.Extensions.Logging; using NBitcoin; using NBitcoin.Crypto; -using LightningPayment = BTCPayApp.CommonServer.Models.LightningPayment; namespace BTCPayApp.Core.Attempt2; +public static class AppToServerHelper +{ + + public static LightningInvoice ToInvoice(this AppLightningPayment lightningPayment) + { + return new LightningInvoice() + { + Id = lightningPayment.PaymentHash.ToString(), + Amount = lightningPayment.Value, + PaymentHash = lightningPayment.PaymentHash.ToString(), + Preimage = lightningPayment.Preimage, + PaidAt = lightningPayment.Status == LightningPaymentStatus.Complete? DateTimeOffset.UtcNow: null, //TODO: store these in ln payment + BOLT11 = lightningPayment.PaymentRequest.ToString(), + Status = lightningPayment.Status == LightningPaymentStatus.Complete? LightningInvoiceStatus.Paid: lightningPayment.PaymentRequest.ExpiryDate < DateTimeOffset.UtcNow? LightningInvoiceStatus.Expired: LightningInvoiceStatus.Unpaid + }; + } + + public static LightningPayment ToPayment(this AppLightningPayment lightningPayment) + { + return new LightningPayment() + { + Id = lightningPayment.PaymentHash.ToString(), + Amount = LightMoney.MilliSatoshis(lightningPayment.Value), + PaymentHash = lightningPayment.PaymentHash.ToString(), + Preimage = lightningPayment.Preimage, + BOLT11 = lightningPayment.PaymentRequest.ToString(), + Status = lightningPayment.Status + }; + } + + public static async Task> ToPayments(this Task> appLightningPayments) + { + var result = await appLightningPayments; + return result.Select(ToPayment).ToList(); + } + public static async Task> ToInvoices(this Task> appLightningPayments) + { + var result = await appLightningPayments; + return result.Select(ToInvoice).ToList(); + } +} + public class BTCPayAppServerClient(ILogger logger, IServiceProvider serviceProvider) : IBTCPayAppHubClient { public event AsyncEventHandler? OnNewBlock; @@ -53,36 +94,36 @@ public async Task NewBlock(string block) private PaymentsManager PaymentsManager => serviceProvider.GetRequiredService().Node.PaymentsManager; - public async Task CreateInvoice(CreateLightningInvoiceRequest createLightningInvoiceRequest) + public async Task CreateInvoice(CreateLightningInvoiceRequest createLightningInvoiceRequest) { var descHash = new uint256(Hashes.SHA256(Encoding.UTF8.GetBytes(createLightningInvoiceRequest.Description)), false); - return await PaymentsManager.RequestPayment(createLightningInvoiceRequest.Amount, - createLightningInvoiceRequest.Expiry, descHash); + return (await PaymentsManager.RequestPayment(createLightningInvoiceRequest.Amount, + createLightningInvoiceRequest.Expiry, descHash)).ToInvoice(); } - public async Task GetLightningInvoice(string paymentHash) + public async Task GetLightningInvoice(uint256 paymentHash) { var invs = await PaymentsManager.List(payments => payments.Where(payment => payment.Inbound && payment.PaymentHash == paymentHash)); - return invs.FirstOrDefault(); + return invs.FirstOrDefault()?.ToInvoice(); } - public async Task GetLightningPayment(string paymentHash) + public async Task GetLightningPayment(uint256 paymentHash) { var invs = await PaymentsManager.List(payments => payments.Where(payment => !payment.Inbound && payment.PaymentHash == paymentHash)); - return invs.FirstOrDefault(); + return invs.FirstOrDefault()?.ToPayment(); } public async Task> GetLightningPayments(ListPaymentsParams request) { - return await PaymentsManager.List(payments => payments.Where(payment => !payment.Inbound), default); + return await PaymentsManager.List(payments => payments.Where(payment => !payment.Inbound), default).ToPayments(); } - public async Task> GetLightningInvoices(ListInvoicesParams request) + public async Task> GetLightningInvoices(ListInvoicesParams request) { - return await PaymentsManager.List(payments => payments.Where(payment => payment.Inbound), default); + return await PaymentsManager.List(payments => payments.Where(payment => payment.Inbound), default).ToInvoices(); } public async Task PayInvoice(string bolt11, long? amountMilliSatoshi) @@ -94,21 +135,21 @@ public async Task PayInvoice(string bolt11, long? amountMilliSatosh var result = await PaymentsManager.PayInvoice(bolt, amountMilliSatoshi is null ? null : LightMoney.MilliSatoshis(amountMilliSatoshi.Value)); return new PayResponse() + { + Result = result.Status switch + { + LightningPaymentStatus.Unknown => PayResult.Unknown, + LightningPaymentStatus.Pending => PayResult.Unknown, + LightningPaymentStatus.Complete => PayResult.Ok, + LightningPaymentStatus.Failed => PayResult.Error, + _ => throw new ArgumentOutOfRangeException() + }, + Details = new PayDetails() { - Result = result.Status switch - { - LightningPaymentStatus.Unknown => PayResult.Unknown, - LightningPaymentStatus.Pending => PayResult.Unknown, - LightningPaymentStatus.Complete => PayResult.Ok, - LightningPaymentStatus.Failed => PayResult.Error, - _ => throw new ArgumentOutOfRangeException() - }, - Details = new PayDetails() - { - Preimage = result.Preimage is not null ? new uint256(result.Preimage) : null, - Status = result.Status - } - }; + Preimage = result.Preimage is not null ? new uint256(result.Preimage) : null, + Status = result.Status + } + }; } catch (Exception e) { diff --git a/BTCPayApp.Core/Attempt2/BTCPayConnectionManager.cs b/BTCPayApp.Core/Attempt2/BTCPayConnectionManager.cs index 7486122..11de209 100644 --- a/BTCPayApp.Core/Attempt2/BTCPayConnectionManager.cs +++ b/BTCPayApp.Core/Attempt2/BTCPayConnectionManager.cs @@ -148,8 +148,12 @@ private async Task StartOrReplace() var account = _accountManager.GetAccount(); if (account is null) return; + Connection = new HubConnectionBuilder() - .AddNewtonsoftJsonProtocol() + .AddNewtonsoftJsonProtocol(options => + { + NBitcoin.JsonConverters.Serializer.RegisterFrontConverters(options.PayloadSerializerSettings); + }) .WithUrl(new Uri(new Uri(account.BaseUri), "hub/btcpayapp").ToString(), options => { options.AccessTokenProvider = () => Task.FromResult(_accountManager.GetAccount()?.AccessToken); diff --git a/BTCPayApp.Core/Attempt2/BTCPayPaymentsNotifier.cs b/BTCPayApp.Core/Attempt2/BTCPayPaymentsNotifier.cs index ede8748..b5c1896 100644 --- a/BTCPayApp.Core/Attempt2/BTCPayPaymentsNotifier.cs +++ b/BTCPayApp.Core/Attempt2/BTCPayPaymentsNotifier.cs @@ -1,8 +1,7 @@ -using BTCPayApp.CommonServer.Models; -using BTCPayApp.Core.Data; +using BTCPayApp.Core.Data; using BTCPayApp.Core.Helpers; using BTCPayApp.Core.LDK; -using Microsoft.Extensions.Logging; +using BTCPayServer.Lightning; namespace BTCPayApp.Core.Attempt2; @@ -26,14 +25,16 @@ public async Task StartAsync(CancellationToken cancellationToken) _paymentsManager.OnPaymentUpdate += OnPaymentUpdate; } - private async Task OnPaymentUpdate(object? sender, LightningPayment e) + private async Task OnPaymentUpdate(object? sender, AppLightningPayment e) { await _connectionManager.HubProxy - .SendPaymentUpdate( - _onChainWalletManager.WalletConfig.Derivations[WalletDerivation.LightningScripts].Identifier, e) + .SendInvoiceUpdate( + _onChainWalletManager.WalletConfig.Derivations[WalletDerivation.LightningScripts].Identifier, e.ToInvoice()) .RunSync(); } + + public async Task StopAsync(CancellationToken cancellationToken) { _paymentsManager.OnPaymentUpdate -= OnPaymentUpdate; diff --git a/BTCPayApp.Core/Attempt2/LightningNodeService.cs b/BTCPayApp.Core/Attempt2/LightningNodeService.cs index 92e5bd0..a47c84a 100644 --- a/BTCPayApp.Core/Attempt2/LightningNodeService.cs +++ b/BTCPayApp.Core/Attempt2/LightningNodeService.cs @@ -120,7 +120,6 @@ public async Task CleanseTask() await _onChainWalletManager.RemoveDerivation(WalletDerivation.LightningScripts); await using var context = await _dbContextFactory.CreateDbContextAsync(); context.LightningPayments.RemoveRange(context.LightningPayments); - context.LightningChannels.RemoveRange(context.LightningChannels); context.Settings.RemoveRange(context.Settings.Where(s => new string[]{"ChannelManager","NetworkGraph","Score","lightningconfig"}.Contains(s.Key))); await context.SaveChangesAsync(); diff --git a/BTCPayApp.Core/BTCPayApp.Core.csproj b/BTCPayApp.Core/BTCPayApp.Core.csproj index 2887492..ec6fa30 100644 --- a/BTCPayApp.Core/BTCPayApp.Core.csproj +++ b/BTCPayApp.Core/BTCPayApp.Core.csproj @@ -12,8 +12,6 @@ - - @@ -38,6 +36,7 @@ + diff --git a/BTCPayApp.Core/Data/AppDbContext.cs b/BTCPayApp.Core/Data/AppDbContext.cs index 9572ea0..6416f9c 100644 --- a/BTCPayApp.Core/Data/AppDbContext.cs +++ b/BTCPayApp.Core/Data/AppDbContext.cs @@ -1,7 +1,16 @@ using System.ComponentModel.DataAnnotations; using System.Text.Json; using BTCPayApp.CommonServer.Models; +using BTCPayApp.Core.JsonConverters; +using BTCPayApp.Core.LDK; +using BTCPayServer.Lightning; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.ChangeTracking; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NBitcoin; +using Newtonsoft.Json; +using JsonSerializer = System.Text.Json.JsonSerializer; namespace BTCPayApp.Core.Data; @@ -14,27 +23,61 @@ public AppDbContext(DbContextOptions options) : base(options) public DbSet Settings { get; set; } public DbSet LightningChannels { get; set; } - public DbSet LightningPayments { get; set; } + public DbSet LightningPayments { get; set; } // public DbSet SpendableCoins { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { - //TODO: add paymentId to the primary key and generate a random one if not provided - modelBuilder.Entity() - .HasKey(w => new {w.PaymentHash, w.Inbound, w.PaymentId}); -//we use system.text.json because it is natively supported in efcore for querying - modelBuilder.Entity().Property(p => p.AdditionalData) + modelBuilder.Entity().Property(payment => payment.PaymentRequest) + .HasConversion( + request => request.ToString(), + str => NetworkHelper.Try(network => BOLT11PaymentRequest.Parse(str, network))); + + modelBuilder.Entity().Property(payment => payment.Secret) + .HasConversion( + request => request.ToString(), + str =>uint256.Parse(str)); + + modelBuilder.Entity().Property(payment => payment.PaymentHash) .HasConversion( - v => JsonSerializer.Serialize(v, JsonSerializerOptions.Default), - v => JsonSerializer.Deserialize>(v, JsonSerializerOptions.Default)!); + request => request.ToString(), + str =>uint256.Parse(str)); + + modelBuilder.Entity().Property(payment => payment.Value) + .HasConversion( + request => request.MilliSatoshi, + str => new LightMoney(str)); + + modelBuilder.Entity().Property(payment => payment.AdditionalData).HasJsonConversion(); + modelBuilder.Entity() + .HasKey(w => new {w.PaymentHash, w.Inbound, w.PaymentId}); base.OnModelCreating(modelBuilder); } } -// public class SpendableCoin -// { -// public string Script { get; set; } -// [Key] public string Outpoint { get; set; } -// public byte[] Data { get; set; } -// } \ No newline at end of file +public static class ValueConversionExtensions +{ + public static PropertyBuilder HasJsonConversion(this PropertyBuilder propertyBuilder) where T : class, new() + { + var converter = new ValueConverter + ( + v => JsonSerializer.Serialize(v, JsonSerializerOptions.Default), + v => JsonSerializer.Deserialize(v, JsonSerializerOptions.Default) ?? new T() + ); + + var comparer = new ValueComparer + ( + (l, r) => JsonSerializer.Serialize(l,JsonSerializerOptions.Default) == JsonSerializer.Serialize(r,JsonSerializerOptions.Default), + v => v == null ? 0 : JsonConvert.SerializeObject(v).GetHashCode(), + v => JsonSerializer.Deserialize(JsonSerializer.Serialize(v,JsonSerializerOptions.Default), JsonSerializerOptions.Default)! + ); + + propertyBuilder.HasConversion(converter); + propertyBuilder.Metadata.SetValueConverter(converter); + propertyBuilder.Metadata.SetValueComparer(comparer); + propertyBuilder.HasColumnType("jsonb"); + + return propertyBuilder; + } +} \ No newline at end of file diff --git a/BTCPayApp.Core/Data/Setting.cs b/BTCPayApp.Core/Data/Setting.cs index 1c9208b..e267596 100644 --- a/BTCPayApp.Core/Data/Setting.cs +++ b/BTCPayApp.Core/Data/Setting.cs @@ -7,4 +7,4 @@ public class Setting [Key] public string Key { get; set; } public byte[] Value { get; set; } -} \ No newline at end of file +} diff --git a/BTCPayApp.Core/DatabaseConfigProvider.cs b/BTCPayApp.Core/DatabaseConfigProvider.cs index bdbfe78..6fa958b 100644 --- a/BTCPayApp.Core/DatabaseConfigProvider.cs +++ b/BTCPayApp.Core/DatabaseConfigProvider.cs @@ -1,10 +1,88 @@ -using System.Text.Json; +using System.Collections.Concurrent; +using System.Text.Json; +using BTCPayApp.Core.Attempt2; using BTCPayApp.Core.Contracts; using BTCPayApp.Core.Data; +using BTCPayServer.Lightning; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.Extensions.Logging; namespace BTCPayApp.Core; + + +public class VSSMapperInterceptor : SaveChangesInterceptor +{ + + public VSSMapperInterceptor(BTCPayConnectionManager btcPayConnectionManager, ILogger logger) + { + } + + private ConcurrentDictionary PendingEvents = new ConcurrentDictionary(); + public override ValueTask SavedChangesAsync(SaveChangesCompletedEventData eventData, int result, + CancellationToken cancellationToken = new CancellationToken()) + { + return base.SavedChangesAsync(eventData, result, cancellationToken); + } + + public override ValueTask> SavingChangesAsync(DbContextEventData eventData, InterceptionResult result, + CancellationToken cancellationToken = new CancellationToken()) + { + foreach (var entry in eventData.Context.ChangeTracker.Entries()) + { + if (entry.Entity is LightningPayment lightningPayment) + { + } + if (entry.Entity is Channel channel) + { + + } + if (entry.Entity is Setting setting) + { + + } + } + + return base.SavingChangesAsync(eventData, result, cancellationToken); + } + + public override Task SaveChangesCanceledAsync(DbContextEventData eventData, + CancellationToken cancellationToken = new CancellationToken()) + { + PendingEvents.Remove(eventData.EventId, out _); + return base.SaveChangesCanceledAsync(eventData, cancellationToken); + } + + public override Task SaveChangesFailedAsync(DbContextErrorEventData eventData, + CancellationToken cancellationToken = new CancellationToken()) + { + PendingEvents.Remove(eventData.EventId, out _); + return base.SaveChangesFailedAsync(eventData, cancellationToken); + } + + +} + +public static class EFExtensions +{ + + public static async Task CrappyUpsert(this DbContext ctx, T item, CancellationToken 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); + } + } +} + public class DatabaseConfigProvider: IConfigProvider { private readonly IDbContextFactory _dbContextFactory; @@ -40,7 +118,8 @@ public async Task Set(string key, T? value) } var newValue = typeof(T) == typeof(byte[])? value as byte[]:JsonSerializer.SerializeToUtf8Bytes(value); - await dbContext.Upsert(new Setting {Key = key, Value = newValue}).RunAsync(); + var setting = new Setting {Key = key, Value = newValue}; + await dbContext.CrappyUpsert(setting, CancellationToken.None); } diff --git a/BTCPayApp.Core/JsonConverters/UInt256JsonConverter.cs b/BTCPayApp.Core/JsonConverters/UInt256JsonConverter.cs new file mode 100644 index 0000000..082d35b --- /dev/null +++ b/BTCPayApp.Core/JsonConverters/UInt256JsonConverter.cs @@ -0,0 +1,116 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using BTCPayServer.Lightning; +using NBitcoin; + +namespace BTCPayApp.Core.JsonConverters; + +public class DateTimeToUnixTimeConverter : JsonConverter +{ + public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + switch (reader.TokenType) + { + case JsonTokenType.Null: + return default; + case JsonTokenType.Number: + return DateTimeOffset.FromUnixTimeSeconds(reader.GetInt64()); + case JsonTokenType.String: + return DateTimeOffset.FromUnixTimeSeconds(long.Parse(reader.GetString())); + } + + throw new JsonException("Expected number or string with a unix timestamp value"); + } + + public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options) + { + writer.WriteNumberValue(value.ToUnixTimeSeconds()); + } +} + +public class LightMoneyJsonConverter : JsonConverter +{ + public override LightMoney? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return reader.TokenType switch + { + JsonTokenType.String => LightMoney.Parse(reader.GetString()), + JsonTokenType.Null => null, + _ => throw new ArgumentOutOfRangeException() + }; + } + + public override void Write(Utf8JsonWriter writer, LightMoney value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString()); + } +} + +public class UInt256JsonConverter : JsonConverter +{ + public override uint256? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Null) + { + return null; + } + + if (reader.TokenType != JsonTokenType.String) + { + throw new JsonException("Expected string"); + } + + return uint256.Parse(reader.GetString()); + } + + public override void Write(Utf8JsonWriter writer, uint256 value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString()); + } +} + +public static class NetworkHelper +{ + public static T Try(Func func) + { + Exception? lastException = null; + foreach (var network in Network.GetNetworks()) + { + try + { + return func.Invoke(network); + } + catch (Exception e) + { + lastException = e; + } + } + + throw lastException!; + } +} + +public class BOLT11PaymentRequestJsonConverter : JsonConverter +{ + public override BOLT11PaymentRequest? Read(ref Utf8JsonReader reader, Type typeToConvert, + JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Null) + { + return null; + } + + if (reader.TokenType != JsonTokenType.String) + { + throw new JsonException("Expected string"); + } + + var str = reader.GetString(); + return NetworkHelper.Try(network => BOLT11PaymentRequest.Parse(str, network)); + } + + public override void Write(Utf8JsonWriter writer, BOLT11PaymentRequest value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString()); + } +} \ No newline at end of file diff --git a/BTCPayApp.Core/LDK/PaymentsManager.cs b/BTCPayApp.Core/LDK/PaymentsManager.cs index 3a8cc45..c7875a3 100644 --- a/BTCPayApp.Core/LDK/PaymentsManager.cs +++ b/BTCPayApp.Core/LDK/PaymentsManager.cs @@ -1,19 +1,46 @@ using System.Collections.Concurrent; using System.Security.Cryptography; using System.Text.Json; +using System.Text.Json.Serialization; using BTCPayApp.Core.Attempt2; using BTCPayApp.Core.Data; using BTCPayApp.Core.Helpers; +using BTCPayApp.Core.JsonConverters; using BTCPayApp.Core.LSP.JIT; using BTCPayServer.Lightning; using Microsoft.EntityFrameworkCore; using NBitcoin; using org.ldk.structs; using org.ldk.util; -// using BTCPayServer.Lightning; -using LightningPayment = BTCPayApp.CommonServer.Models.LightningPayment; + namespace BTCPayApp.Core.LDK; +public partial class AppLightningPayment +{ + + [JsonConverter(typeof(UInt256JsonConverter))] + public uint256 PaymentHash { get; set; } + public string PaymentId { get; set; } + public string? Preimage { get; set; } + + [JsonConverter(typeof(UInt256JsonConverter))] + public uint256 Secret { get; set; } + public bool Inbound { get; set; } + [JsonConverter(typeof(DateTimeToUnixTimeConverter))] + public DateTimeOffset Timestamp { get; set; } + + [JsonConverter(typeof(LightMoneyJsonConverter))] + public LightMoney Value { get; set; } + [JsonConverter(typeof(JsonStringEnumConverter))] + public LightningPaymentStatus Status { get; set; } + [JsonConverter(typeof(BOLT11PaymentRequestJsonConverter))] + public BOLT11PaymentRequest PaymentRequest { get; set; } + + [JsonExtensionData] public Dictionary AdditionalData { get; set; } = new(); + +} + + public class PaymentsManager : IScopedHostedService, @@ -52,8 +79,8 @@ public PaymentsManager( _ldkNode = ldkNode; } - public async Task> List( - Func, IQueryable> filter, + public async Task> List( + Func, IQueryable> filter, CancellationToken cancellationToken = default) { await using var context = await _dbContextFactory.CreateDbContextAsync(cancellationToken); @@ -77,7 +104,7 @@ public async Task> List( // var invoice = Bolt11Invoice.from_signed(_channelManager, _nodeSigner, _logger, _network.GetLdkCurrency(), // } - public async Task RequestPayment(LightMoney amount, TimeSpan expiry, uint256 descriptionHash) + public async Task RequestPayment(LightMoney amount, TimeSpan expiry, uint256 descriptionHash) { var amt = amount == LightMoney.Zero ? Option_u64Z.none() : Option_u64Z.some(amount.MilliSatoshi); @@ -126,23 +153,22 @@ public async Task RequestPayment(LightMoney amount, TimeSpan e _ => throw new Exception("Unknown error retrieving preimage") }; - var parsedOriginalInvoice= BOLT11PaymentRequest.Parse(originalInvoice.to_str(), _network); - var lp = new LightningPayment() + var lp = new AppLightningPayment() { Inbound = true, PaymentId = "default", Value = amount.MilliSatoshi, - PaymentHash = parsedOriginalInvoice.PaymentHash!.ToString(), - Secret = parsedOriginalInvoice.PaymentSecret!.ToString(), + PaymentHash = parsedOriginalInvoice.PaymentHash!, + Secret = parsedOriginalInvoice.PaymentSecret!, Preimage = Convert.ToHexString(preimage!).ToLower(), Status = LightningPaymentStatus.Pending, Timestamp = now, - PaymentRequests = [parsedOriginalInvoice.ToString()], - AdditionalData = new Dictionary() + PaymentRequest = parsedOriginalInvoice, + AdditionalData = new Dictionary() { - [LightningPaymentDescriptionKey] = JsonSerializer.SerializeToDocument(descriptionHash.ToString()), - [LightningPaymentExpiryKey] = JsonSerializer.SerializeToDocument(originalInvoice.expires_at()) + [LightningPaymentDescriptionKey] = JsonSerializer.SerializeToElement(descriptionHash.ToString()), + [LightningPaymentExpiryKey] = JsonSerializer.SerializeToElement(now.Add(expiry)) } }; @@ -162,7 +188,7 @@ public async Task RequestPayment(LightMoney amount, TimeSpan e return lp; } - public async Task PayInvoice(BOLT11PaymentRequest paymentRequest, + public async Task PayInvoice(BOLT11PaymentRequest paymentRequest, LightMoney? explicitAmount = null) { var id = RandomUtils.GetBytes(32); @@ -175,15 +201,15 @@ public async Task PayInvoice(BOLT11PaymentRequest paymentReque //check if we have a db record with same pay hash but has the preimage set -var payHash = Convert.ToHexString(invoice.payment_hash()).ToLower(); -var paySecret = Convert.ToHexString(invoice.payment_secret()).ToLower(); +var payHash = new uint256(invoice.payment_hash()); +var paySecret = new uint256(invoice.payment_secret()); await using var context = await _dbContextFactory.CreateDbContextAsync(); var inbound = await context.LightningPayments.FirstOrDefaultAsync(lightningPayment => lightningPayment.PaymentHash == payHash && lightningPayment.Inbound); if (inbound is not null) { - var newOutbound = new LightningPayment() + var newOutbound = new AppLightningPayment() { Inbound = false, Value = amt, @@ -192,7 +218,7 @@ public async Task PayInvoice(BOLT11PaymentRequest paymentReque Status = LightningPaymentStatus.Complete, Timestamp = DateTimeOffset.UtcNow, PaymentId = Convert.ToHexString(id).ToLower(), - PaymentRequests = [invoiceStr], + PaymentRequest = paymentRequest, Preimage = inbound.Preimage }; await context.LightningPayments.AddAsync(newOutbound); @@ -211,7 +237,7 @@ public async Task PayInvoice(BOLT11PaymentRequest paymentReque return newOutbound; } - var outbound = new LightningPayment() + var outbound = new AppLightningPayment() { Inbound = false, Value = amt, @@ -220,7 +246,7 @@ public async Task PayInvoice(BOLT11PaymentRequest paymentReque Status = LightningPaymentStatus.Pending, Timestamp = DateTimeOffset.UtcNow, PaymentId = Convert.ToHexString(id).ToLower(), - PaymentRequests = [invoiceStr], + PaymentRequest = paymentRequest, }; await context.LightningPayments.AddAsync(outbound); await context.SaveChangesAsync(); @@ -257,41 +283,46 @@ public async Task PayInvoice(BOLT11PaymentRequest paymentReque return outbound; } - public async Task Cancel(string id, bool inbound) + public async Task CancelInbound(uint256 paymentHash) { - if (!inbound) + + await using var context = await _dbContextFactory.CreateDbContextAsync(); + var payment = await context.LightningPayments.FirstOrDefaultAsync(lightningPayment => + lightningPayment.Status == LightningPaymentStatus.Pending && lightningPayment.Inbound && lightningPayment.PaymentHash == paymentHash); + if (payment is not null) { - await Task.Run(() => _channelManager.abandon_payment(Convert.FromHexString(id)) ); - - // return; + payment.Status = LightningPaymentStatus.Failed; + await context.SaveChangesAsync(); } + } + public async Task CancelOutbound(string paymentId) + { + + await Task.Run(() => _channelManager.abandon_payment(Convert.FromHexString(paymentId)) ); await using var context = await _dbContextFactory.CreateDbContextAsync(); var payment = await context.LightningPayments.FirstOrDefaultAsync(lightningPayment => lightningPayment.Status == LightningPaymentStatus.Pending && - - ((inbound && lightningPayment.Inbound && lightningPayment.PaymentHash == id) || - (!inbound && !lightningPayment.Inbound && lightningPayment.PaymentId == id))); + !lightningPayment.Inbound && lightningPayment.PaymentId == paymentId); if (payment is not null) { payment.Status = LightningPaymentStatus.Failed; await context.SaveChangesAsync(); } - } - private async Task Payment(LightningPayment lightningPayment, CancellationToken cancellationToken = default) + private async Task Payment(AppLightningPayment lightningPayment, CancellationToken cancellationToken = default) { await using var context = await _dbContextFactory.CreateDbContextAsync(cancellationToken); - var x = await context.LightningPayments.Upsert(lightningPayment).RunAsync(cancellationToken); + var x = await context.CrappyUpsert(lightningPayment, cancellationToken); if (x > 0) { OnPaymentUpdate?.Invoke(this, lightningPayment); } } - private async Task PaymentUpdate(string paymentHash, bool inbound, string paymentId, bool failure, + private async Task PaymentUpdate(uint256 paymentHash, bool inbound, string paymentId, bool failure, string? preimage, CancellationToken cancellationToken = default) { await using var context = await _dbContextFactory.CreateDbContextAsync(cancellationToken); @@ -320,13 +351,14 @@ private async Task PaymentUpdate(string paymentHash, bool inbound, string paymen } } - public AsyncEventHandler? OnPaymentUpdate { get; set; } + public AsyncEventHandler? OnPaymentUpdate { get; set; } public async Task Handle(Event.Event_PaymentClaimable eventPaymentClaimable) { await using var context = await _dbContextFactory.CreateDbContextAsync(); + var paymentHash = new uint256(eventPaymentClaimable.payment_hash); var accept = await context.LightningPayments.FirstOrDefaultAsync(payment => - payment.PaymentHash == Convert.ToHexString(eventPaymentClaimable.payment_hash).ToLower() && + payment.PaymentHash == paymentHash && payment.Inbound && payment.Status == LightningPaymentStatus.Pending); @@ -375,18 +407,18 @@ public async Task Handle(Event.Event_PaymentClaimed eventPaymentClaimed) { var preimage = eventPaymentClaimed.purpose.GetPreimage(out var secret); - await PaymentUpdate( Convert.ToHexString(eventPaymentClaimed.payment_hash).ToLower(), true, "default", false, preimage is null ? null : Convert.ToHexString(preimage).ToLower()); + await PaymentUpdate( new uint256(eventPaymentClaimed.payment_hash), true, "default", false, preimage is null ? null : Convert.ToHexString(preimage).ToLower()); } public async Task Handle(Event.Event_PaymentFailed @eventPaymentFailed) { - await PaymentUpdate(Convert.ToHexString(eventPaymentFailed.payment_hash).ToLower(), false, + await PaymentUpdate(new uint256(eventPaymentFailed.payment_hash), false, Convert.ToHexString(eventPaymentFailed.payment_id).ToLower(), true, null); } public async Task Handle(Event.Event_PaymentSent eventPaymentSent) { - await PaymentUpdate(Convert.ToHexString(eventPaymentSent.payment_hash).ToLower(), false, + await PaymentUpdate(new uint256(eventPaymentSent.payment_hash), false, Convert.ToHexString( ((Option_ThirtyTwoBytesZ.Option_ThirtyTwoBytesZ_Some) eventPaymentSent.payment_id).some).ToLower(), false, Convert.ToHexString(eventPaymentSent.payment_preimage).ToLower()); diff --git a/BTCPayApp.Core/LSP/JIT/Flow2Client.cs b/BTCPayApp.Core/LSP/JIT/Flow2Client.cs index c6ff046..3547697 100644 --- a/BTCPayApp.Core/LSP/JIT/Flow2Client.cs +++ b/BTCPayApp.Core/LSP/JIT/Flow2Client.cs @@ -1,6 +1,5 @@ using System.Net; using System.Text.Json; -using AngleSharp.Dom; using BTCPayApp.Core.Attempt2; using BTCPayApp.Core.Data; using BTCPayApp.Core.Helpers; @@ -10,9 +9,7 @@ using NBitcoin; using Newtonsoft.Json; using org.ldk.structs; -using org.ldk.util; using JsonSerializer = System.Text.Json.JsonSerializer; -using LightningPayment = BTCPayApp.CommonServer.Models.LightningPayment; namespace BTCPayApp.Core.LSP.JIT; @@ -112,13 +109,9 @@ public async Task GetProposal(BOLT11PaymentRequest bolt11P public const string LightningPaymentJITFeeKey = "JITFeeKey"; public const string LightningPaymentLSPKey = "LSP"; - public async Task WrapInvoice(LightningPayment lightningPayment, JITFeeResponse? fee = null) + public const string LightningPaymentOriginalPaymentRequest = "OriginalPaymentRequest"; + public async Task WrapInvoice(AppLightningPayment lightningPayment, JITFeeResponse? fee) { - - if (lightningPayment.PaymentRequests.Count > 1) - { - return false; - } if(lightningPayment.AdditionalData?.ContainsKey(LightningPaymentLSPKey) is true) return false; @@ -127,28 +120,24 @@ public async Task WrapInvoice(LightningPayment lightningPayment, JITFeeRes if(fee is null) return false; - var invoice = BOLT11PaymentRequest.Parse(lightningPayment.PaymentRequests[0], _network); + var invoice = lightningPayment.PaymentRequest; var proposal = await GetProposal(invoice,null, fee!.FeeIdentifier); if(proposal.MinimumAmount != fee.AmountToRequestPayer || proposal.PaymentHash != invoice.PaymentHash) return false; - - lightningPayment.PaymentRequests.Insert(0, proposal.ToString()); - lightningPayment.AdditionalData ??= new Dictionary(); - lightningPayment.AdditionalData[LightningPaymentLSPKey] = JsonSerializer.SerializeToDocument(ProviderName); - lightningPayment.AdditionalData[LightningPaymentJITFeeKey] = JsonSerializer.SerializeToDocument(fee); + lightningPayment.PaymentRequest = proposal; + lightningPayment.AdditionalData ??= new Dictionary(); + lightningPayment.AdditionalData[LightningPaymentOriginalPaymentRequest] = JsonSerializer.SerializeToElement(invoice); + lightningPayment.AdditionalData[LightningPaymentLSPKey] = JsonSerializer.SerializeToElement(ProviderName); + lightningPayment.AdditionalData[LightningPaymentJITFeeKey] = JsonSerializer.SerializeToElement(fee); return true; } public async Task StartAsync(CancellationToken cancellationToken) { _node.ConfigUpdated += ConfigUpdated; - _ = Task.Run(async () => - { - - await ConfigUpdated(this, await _node.GetConfig()); - }, cancellationToken); + _ = ConfigUpdated(this, await _node.GetConfig()).WithCancellation(cancellationToken); } private FlowInfoResponse? _info; diff --git a/BTCPayApp.Core/LSP/JIT/IJITService.cs b/BTCPayApp.Core/LSP/JIT/IJITService.cs index 51620d5..136bb32 100644 --- a/BTCPayApp.Core/LSP/JIT/IJITService.cs +++ b/BTCPayApp.Core/LSP/JIT/IJITService.cs @@ -1,7 +1,7 @@ -using System.Text.Json; -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; +using BTCPayApp.Core.JsonConverters; +using BTCPayApp.Core.LDK; using BTCPayServer.Lightning; -using LightningPayment = BTCPayApp.CommonServer.Models.LightningPayment; namespace BTCPayApp.Core.LSP.JIT; @@ -9,25 +9,7 @@ public interface IJITService { public string ProviderName { get; } public Task CalculateInvoiceAmount(LightMoney expectedAmount); - public Task WrapInvoice(LightningPayment lightningPayment, JITFeeResponse? feeReponse); -} - -public class LightMoneyJsonConverter : JsonConverter -{ - public override LightMoney? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return reader.TokenType switch - { - JsonTokenType.String => LightMoney.Parse(reader.GetString()), - JsonTokenType.Null => null, - _ => throw new ArgumentOutOfRangeException() - }; - } - - public override void Write(Utf8JsonWriter writer, LightMoney value, JsonSerializerOptions options) - { - writer.WriteStringValue(value.ToString()); - } + public Task WrapInvoice(AppLightningPayment lightningPayment, JITFeeResponse? feeReponse); } public record JITFeeResponse diff --git a/BTCPayApp.Core/Migrations/20240502121610_Init.Designer.cs b/BTCPayApp.Core/Migrations/20240502121610_Init.Designer.cs deleted file mode 100644 index b4d9985..0000000 --- a/BTCPayApp.Core/Migrations/20240502121610_Init.Designer.cs +++ /dev/null @@ -1,88 +0,0 @@ -// -using System; -using BTCPayApp.Core.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -#nullable disable - -namespace BTCPayApp.Core.Migrations -{ - [DbContext(typeof(AppDbContext))] - [Migration("20240502121610_Init")] - partial class Init - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "8.0.4"); - - modelBuilder.Entity("BTCPayApp.Core.Data.Channel", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("Aliases") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Data") - .IsRequired() - .HasColumnType("BLOB"); - - b.HasKey("Id"); - - b.ToTable("LightningChannels"); - }); - - modelBuilder.Entity("BTCPayApp.Core.Data.LightningPayment", b => - { - b.Property("PaymentHash") - .HasColumnType("TEXT"); - - b.Property("Inbound") - .HasColumnType("INTEGER"); - - b.Property("PaymentId") - .HasColumnType("TEXT"); - - b.Property("Preimage") - .HasColumnType("TEXT"); - - b.Property("Secret") - .HasColumnType("TEXT"); - - b.Property("Status") - .HasColumnType("INTEGER"); - - b.Property("Timestamp") - .HasColumnType("TEXT"); - - b.Property("Value") - .HasColumnType("INTEGER"); - - b.HasKey("PaymentHash", "Inbound"); - - b.ToTable("LightningPayments"); - }); - - modelBuilder.Entity("BTCPayApp.Core.Data.Setting", b => - { - b.Property("Key") - .HasColumnType("TEXT"); - - b.Property("Value") - .IsRequired() - .HasColumnType("BLOB"); - - b.HasKey("Key"); - - b.ToTable("Settings"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/BTCPayApp.Core/Migrations/20240508085527_PR.Designer.cs b/BTCPayApp.Core/Migrations/20240508085527_PR.Designer.cs deleted file mode 100644 index 8649bc2..0000000 --- a/BTCPayApp.Core/Migrations/20240508085527_PR.Designer.cs +++ /dev/null @@ -1,92 +0,0 @@ -// -using System; -using BTCPayApp.Core.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -#nullable disable - -namespace BTCPayApp.Core.Migrations -{ - [DbContext(typeof(AppDbContext))] - [Migration("20240508085527_PR")] - partial class PR - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "8.0.4"); - - modelBuilder.Entity("BTCPayApp.CommonServer.LightningPayment", b => - { - b.Property("PaymentHash") - .HasColumnType("TEXT"); - - b.Property("Inbound") - .HasColumnType("INTEGER"); - - b.Property("PaymentId") - .HasColumnType("TEXT"); - - b.Property("PaymentRequests") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Preimage") - .HasColumnType("TEXT"); - - b.Property("Secret") - .HasColumnType("TEXT"); - - b.Property("Status") - .HasColumnType("INTEGER"); - - b.Property("Timestamp") - .HasColumnType("TEXT"); - - b.Property("Value") - .HasColumnType("INTEGER"); - - b.HasKey("PaymentHash", "Inbound"); - - b.ToTable("LightningPayments"); - }); - - modelBuilder.Entity("BTCPayApp.Core.Data.Channel", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("Aliases") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Data") - .IsRequired() - .HasColumnType("BLOB"); - - b.HasKey("Id"); - - b.ToTable("LightningChannels"); - }); - - modelBuilder.Entity("BTCPayApp.Core.Data.Setting", b => - { - b.Property("Key") - .HasColumnType("TEXT"); - - b.Property("Value") - .IsRequired() - .HasColumnType("BLOB"); - - b.HasKey("Key"); - - b.ToTable("Settings"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/BTCPayApp.Core/Migrations/20240508085527_PR.cs b/BTCPayApp.Core/Migrations/20240508085527_PR.cs deleted file mode 100644 index f3f42e0..0000000 --- a/BTCPayApp.Core/Migrations/20240508085527_PR.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace BTCPayApp.Core.Migrations -{ - /// - public partial class PR : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "PaymentRequests", - table: "LightningPayments", - type: "TEXT", - nullable: false, - defaultValue: "[]"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "PaymentRequests", - table: "LightningPayments"); - } - } -} diff --git a/BTCPayApp.Core/Migrations/20240527105733_upd2.Designer.cs b/BTCPayApp.Core/Migrations/20240527105733_upd2.Designer.cs deleted file mode 100644 index 0b87e61..0000000 --- a/BTCPayApp.Core/Migrations/20240527105733_upd2.Designer.cs +++ /dev/null @@ -1,92 +0,0 @@ -// -using System; -using BTCPayApp.Core.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -#nullable disable - -namespace BTCPayApp.Core.Migrations -{ - [DbContext(typeof(AppDbContext))] - [Migration("20240527105733_upd2")] - partial class upd2 - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "8.0.5"); - - modelBuilder.Entity("BTCPayApp.CommonServer.LightningPayment", b => - { - b.Property("PaymentHash") - .HasColumnType("TEXT"); - - b.Property("Inbound") - .HasColumnType("INTEGER"); - - b.Property("PaymentId") - .HasColumnType("TEXT"); - - b.Property("PaymentRequests") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Preimage") - .HasColumnType("TEXT"); - - b.Property("Secret") - .HasColumnType("TEXT"); - - b.Property("Status") - .HasColumnType("INTEGER"); - - b.Property("Timestamp") - .HasColumnType("TEXT"); - - b.Property("Value") - .HasColumnType("INTEGER"); - - b.HasKey("PaymentHash", "Inbound", "PaymentId"); - - b.ToTable("LightningPayments"); - }); - - modelBuilder.Entity("BTCPayApp.Core.Data.Channel", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("Aliases") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Data") - .IsRequired() - .HasColumnType("BLOB"); - - b.HasKey("Id"); - - b.ToTable("LightningChannels"); - }); - - modelBuilder.Entity("BTCPayApp.Core.Data.Setting", b => - { - b.Property("Key") - .HasColumnType("TEXT"); - - b.Property("Value") - .IsRequired() - .HasColumnType("BLOB"); - - b.HasKey("Key"); - - b.ToTable("Settings"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/BTCPayApp.Core/Migrations/20240527105733_upd2.cs b/BTCPayApp.Core/Migrations/20240527105733_upd2.cs deleted file mode 100644 index d181b3e..0000000 --- a/BTCPayApp.Core/Migrations/20240527105733_upd2.cs +++ /dev/null @@ -1,54 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace BTCPayApp.Core.Migrations -{ - /// - public partial class upd2 : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropPrimaryKey( - name: "PK_LightningPayments", - table: "LightningPayments"); - - migrationBuilder.AlterColumn( - name: "PaymentId", - table: "LightningPayments", - type: "TEXT", - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "TEXT", - oldNullable: true); - - migrationBuilder.AddPrimaryKey( - name: "PK_LightningPayments", - table: "LightningPayments", - columns: new[] { "PaymentHash", "Inbound", "PaymentId" }); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropPrimaryKey( - name: "PK_LightningPayments", - table: "LightningPayments"); - - migrationBuilder.AlterColumn( - name: "PaymentId", - table: "LightningPayments", - type: "TEXT", - nullable: true, - oldClrType: typeof(string), - oldType: "TEXT"); - - migrationBuilder.AddPrimaryKey( - name: "PK_LightningPayments", - table: "LightningPayments", - columns: new[] { "PaymentHash", "Inbound" }); - } - } -} diff --git a/BTCPayApp.Core/Migrations/20240621120336_adddatatoln.cs b/BTCPayApp.Core/Migrations/20240621120336_adddatatoln.cs deleted file mode 100644 index c738179..0000000 --- a/BTCPayApp.Core/Migrations/20240621120336_adddatatoln.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace BTCPayApp.Core.Migrations -{ - /// - public partial class adddatatoln : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "AdditionalData", - table: "LightningPayments", - type: "TEXT", - nullable: false, - defaultValue: ""); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "AdditionalData", - table: "LightningPayments"); - } - } -} diff --git a/BTCPayApp.Core/Migrations/20240621120336_adddatatoln.Designer.cs b/BTCPayApp.Core/Migrations/20240627110659_Init.Designer.cs similarity index 90% rename from BTCPayApp.Core/Migrations/20240621120336_adddatatoln.Designer.cs rename to BTCPayApp.Core/Migrations/20240627110659_Init.Designer.cs index aaf6775..3ce3ae3 100644 --- a/BTCPayApp.Core/Migrations/20240621120336_adddatatoln.Designer.cs +++ b/BTCPayApp.Core/Migrations/20240627110659_Init.Designer.cs @@ -11,8 +11,8 @@ namespace BTCPayApp.Core.Migrations { [DbContext(typeof(AppDbContext))] - [Migration("20240621120336_adddatatoln")] - partial class adddatatoln + [Migration("20240627110659_Init")] + partial class Init { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -20,7 +20,39 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) #pragma warning disable 612, 618 modelBuilder.HasAnnotation("ProductVersion", "8.0.6"); - modelBuilder.Entity("BTCPayApp.CommonServer.Models.LightningPayment", b => + modelBuilder.Entity("BTCPayApp.Core.Data.Channel", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Aliases") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Data") + .IsRequired() + .HasColumnType("BLOB"); + + b.HasKey("Id"); + + b.ToTable("LightningChannels"); + }); + + modelBuilder.Entity("BTCPayApp.Core.Data.Setting", b => + { + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasColumnType("BLOB"); + + b.HasKey("Key"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("BTCPayApp.Core.LDK.AppLightningPayment", b => { b.Property("PaymentHash") .HasColumnType("TEXT"); @@ -33,9 +65,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("AdditionalData") .IsRequired() - .HasColumnType("TEXT"); + .HasColumnType("jsonb"); - b.Property("PaymentRequests") + b.Property("PaymentRequest") .IsRequired() .HasColumnType("TEXT"); @@ -43,6 +75,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasColumnType("TEXT"); b.Property("Secret") + .IsRequired() .HasColumnType("TEXT"); b.Property("Status") @@ -58,38 +91,6 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("LightningPayments"); }); - - modelBuilder.Entity("BTCPayApp.Core.Data.Channel", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("Aliases") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Data") - .IsRequired() - .HasColumnType("BLOB"); - - b.HasKey("Id"); - - b.ToTable("LightningChannels"); - }); - - modelBuilder.Entity("BTCPayApp.Core.Data.Setting", b => - { - b.Property("Key") - .HasColumnType("TEXT"); - - b.Property("Value") - .IsRequired() - .HasColumnType("BLOB"); - - b.HasKey("Key"); - - b.ToTable("Settings"); - }); #pragma warning restore 612, 618 } } diff --git a/BTCPayApp.Core/Migrations/20240502121610_Init.cs b/BTCPayApp.Core/Migrations/20240627110659_Init.cs similarity index 90% rename from BTCPayApp.Core/Migrations/20240502121610_Init.cs rename to BTCPayApp.Core/Migrations/20240627110659_Init.cs index 75b9f8e..4ced1d7 100644 --- a/BTCPayApp.Core/Migrations/20240502121610_Init.cs +++ b/BTCPayApp.Core/Migrations/20240627110659_Init.cs @@ -29,17 +29,19 @@ protected override void Up(MigrationBuilder migrationBuilder) columns: table => new { PaymentHash = table.Column(type: "TEXT", nullable: false), + PaymentId = table.Column(type: "TEXT", nullable: false), Inbound = table.Column(type: "INTEGER", nullable: false), - PaymentId = table.Column(type: "TEXT", nullable: true), Preimage = table.Column(type: "TEXT", nullable: true), - Secret = table.Column(type: "TEXT", nullable: true), + Secret = table.Column(type: "TEXT", nullable: false), Timestamp = table.Column(type: "TEXT", nullable: false), Value = table.Column(type: "INTEGER", nullable: false), - Status = table.Column(type: "INTEGER", nullable: false) + Status = table.Column(type: "INTEGER", nullable: false), + PaymentRequest = table.Column(type: "TEXT", nullable: false), + AdditionalData = table.Column(type: "jsonb", nullable: false) }, constraints: table => { - table.PrimaryKey("PK_LightningPayments", x => new { x.PaymentHash, x.Inbound }); + table.PrimaryKey("PK_LightningPayments", x => new { x.PaymentHash, x.Inbound, x.PaymentId }); }); migrationBuilder.CreateTable( diff --git a/BTCPayApp.Core/Migrations/AppDbContextModelSnapshot.cs b/BTCPayApp.Core/Migrations/AppDbContextModelSnapshot.cs index 9915d8a..0b314fc 100644 --- a/BTCPayApp.Core/Migrations/AppDbContextModelSnapshot.cs +++ b/BTCPayApp.Core/Migrations/AppDbContextModelSnapshot.cs @@ -17,7 +17,39 @@ protected override void BuildModel(ModelBuilder modelBuilder) #pragma warning disable 612, 618 modelBuilder.HasAnnotation("ProductVersion", "8.0.6"); - modelBuilder.Entity("BTCPayApp.CommonServer.Models.LightningPayment", b => + modelBuilder.Entity("BTCPayApp.Core.Data.Channel", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Aliases") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Data") + .IsRequired() + .HasColumnType("BLOB"); + + b.HasKey("Id"); + + b.ToTable("LightningChannels"); + }); + + modelBuilder.Entity("BTCPayApp.Core.Data.Setting", b => + { + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasColumnType("BLOB"); + + b.HasKey("Key"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("BTCPayApp.Core.LDK.AppLightningPayment", b => { b.Property("PaymentHash") .HasColumnType("TEXT"); @@ -30,9 +62,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("AdditionalData") .IsRequired() - .HasColumnType("TEXT"); + .HasColumnType("jsonb"); - b.Property("PaymentRequests") + b.Property("PaymentRequest") .IsRequired() .HasColumnType("TEXT"); @@ -40,6 +72,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("TEXT"); b.Property("Secret") + .IsRequired() .HasColumnType("TEXT"); b.Property("Status") @@ -55,38 +88,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("LightningPayments"); }); - - modelBuilder.Entity("BTCPayApp.Core.Data.Channel", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("Aliases") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Data") - .IsRequired() - .HasColumnType("BLOB"); - - b.HasKey("Id"); - - b.ToTable("LightningChannels"); - }); - - modelBuilder.Entity("BTCPayApp.Core.Data.Setting", b => - { - b.Property("Key") - .HasColumnType("TEXT"); - - b.Property("Value") - .IsRequired() - .HasColumnType("BLOB"); - - b.HasKey("Key"); - - b.ToTable("Settings"); - }); #pragma warning restore 612, 618 } } From 4526824fb41c4d22ecafc7952d9a5c101e41c6ff Mon Sep 17 00:00:00 2001 From: Kukks Date: Fri, 28 Jun 2024 08:17:26 +0200 Subject: [PATCH 05/14] wip --- BTCPayApp.Core/DatabaseConfigProvider.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/BTCPayApp.Core/DatabaseConfigProvider.cs b/BTCPayApp.Core/DatabaseConfigProvider.cs index 6fa958b..124de2c 100644 --- a/BTCPayApp.Core/DatabaseConfigProvider.cs +++ b/BTCPayApp.Core/DatabaseConfigProvider.cs @@ -3,10 +3,12 @@ using BTCPayApp.Core.Attempt2; using BTCPayApp.Core.Contracts; using BTCPayApp.Core.Data; +using BTCPayApp.VSS; using BTCPayServer.Lightning; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.Extensions.Logging; +using VSSProto; namespace BTCPayApp.Core; @@ -26,13 +28,27 @@ public override ValueTask SavedChangesAsync(SaveChangesCompletedEventData e return base.SavedChangesAsync(eventData, result, cancellationToken); } + private IVSSAPI api; public override ValueTask> SavingChangesAsync(DbContextEventData eventData, InterceptionResult result, CancellationToken cancellationToken = new CancellationToken()) { foreach (var entry in eventData.Context.ChangeTracker.Entries()) { + if (entry.Entity is LightningPayment lightningPayment) { + if (entry.State == EntityState.Deleted) + { + + api.DeleteObjectAsync(new DeleteObjectRequest + { + KeyValue = new KeyValue() + { + + } + Key = $"LightningPayment/{lightningPayment.Id}" + }); + } } if (entry.Entity is Channel channel) { From ee3a83c2356297dbb3aa3b6fa921e78e020f8082 Mon Sep 17 00:00:00 2001 From: Kukks Date: Fri, 28 Jun 2024 15:15:10 +0200 Subject: [PATCH 06/14] wip triggers --- BTCPayApp.Core/BTCPayApp.Core.csproj | 2 + BTCPayApp.Core/Data/AppDbContext.cs | 152 +++++++++++++----- BTCPayApp.Core/Data/AppLightningPayment.cs | 37 +++++ BTCPayApp.Core/Data/Channel.cs | 7 +- BTCPayApp.Core/Data/LightningConfig.cs | 7 - BTCPayApp.Core/Data/PeerInfo.cs | 8 + BTCPayApp.Core/Data/Setting.cs | 4 +- .../Data/ValueConversionExtensions.cs | 36 +++++ BTCPayApp.Core/Data/VersionedData.cs | 10 ++ BTCPayApp.Core/Helpers/ChannelExtensions.cs | 8 +- BTCPayApp.Core/LDK/PaymentsManager.cs | 34 +--- BTCPayApp.Core/LSP/JIT/IJITService.cs | 2 +- BTCPayApp.Core/StartupExtensions.cs | 2 + 13 files changed, 224 insertions(+), 85 deletions(-) create mode 100644 BTCPayApp.Core/Data/AppLightningPayment.cs create mode 100644 BTCPayApp.Core/Data/PeerInfo.cs create mode 100644 BTCPayApp.Core/Data/ValueConversionExtensions.cs create mode 100644 BTCPayApp.Core/Data/VersionedData.cs diff --git a/BTCPayApp.Core/BTCPayApp.Core.csproj b/BTCPayApp.Core/BTCPayApp.Core.csproj index ec6fa30..bfd88b3 100644 --- a/BTCPayApp.Core/BTCPayApp.Core.csproj +++ b/BTCPayApp.Core/BTCPayApp.Core.csproj @@ -12,6 +12,8 @@ + + diff --git a/BTCPayApp.Core/Data/AppDbContext.cs b/BTCPayApp.Core/Data/AppDbContext.cs index 6416f9c..bad6b1a 100644 --- a/BTCPayApp.Core/Data/AppDbContext.cs +++ b/BTCPayApp.Core/Data/AppDbContext.cs @@ -1,16 +1,12 @@ using System.ComponentModel.DataAnnotations; -using System.Text.Json; using BTCPayApp.CommonServer.Models; using BTCPayApp.Core.JsonConverters; using BTCPayApp.Core.LDK; using BTCPayServer.Lightning; +using Laraue.EfCoreTriggers.Common.Extensions; using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.ChangeTracking; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.Extensions.Hosting; using NBitcoin; -using Newtonsoft.Json; -using JsonSerializer = System.Text.Json.JsonSerializer; namespace BTCPayApp.Core.Data; @@ -24,60 +20,142 @@ public AppDbContext(DbContextOptions options) : base(options) public DbSet LightningChannels { get; set; } public DbSet LightningPayments { get; set; } - // public DbSet SpendableCoins { get; set; } + public DbSet OutboxItems { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity().Property(payment => payment.PaymentRequest) .HasConversion( - request => request.ToString(), + request => request.ToString(), str => NetworkHelper.Try(network => BOLT11PaymentRequest.Parse(str, network))); - + modelBuilder.Entity().Property(payment => payment.Secret) .HasConversion( - request => request.ToString(), - str =>uint256.Parse(str)); - + request => request.ToString(), + str => uint256.Parse(str)); + modelBuilder.Entity().Property(payment => payment.PaymentHash) .HasConversion( - request => request.ToString(), - str =>uint256.Parse(str)); - + request => request.ToString(), + str => uint256.Parse(str)); + modelBuilder.Entity().Property(payment => payment.Value) .HasConversion( - request => request.MilliSatoshi, + request => request.MilliSatoshi, str => new LightMoney(str)); modelBuilder.Entity().Property(payment => payment.AdditionalData).HasJsonConversion(); modelBuilder.Entity() .HasKey(w => new {w.PaymentHash, w.Inbound, w.PaymentId}); + + + //handling versioned data + modelBuilder.Entity().AfterDelete(trigger => trigger.Action(group => group.Insert( + @ref => new Outbox() + { + Version = @ref.Old.Version, + Key = "Channel-" + @ref.Old.Id, + ActionType = "delete" + }))); + + foreach (var entityType in modelBuilder.Model.GetEntityTypes()) + { + if(typeof(VersionedData).IsAssignableFrom(entityType.ClrType)) + { + var builder = modelBuilder.Entity(entityType.ClrType); + + builder.Property("Version").IsConcurrencyToken().HasDefaultValue(0); + } + } + + modelBuilder.Entity() + .BeforeUpdate(trigger => trigger + .Action(action => action + .Condition(refs => refs.Old.Id == refs.New.Id) + .Update( + (tableRefs, entity) => tableRefs.Old.Id == entity.Id, + (tableRefs, oldChannel) => new Channel() {Version = oldChannel.Version + 1}) + .Insert(insert => new Outbox() + { + Key = "Channel-" + insert.New.Id, + Version = insert.New.Version, + ActionType = "update", + Timestamp = DateTimeOffset.UtcNow + })) + .Action(action => action + .Condition(refs => refs.Old.Id != refs.New.Id) + .Insert(insert => new Outbox() + { + Key = "Channel-" + insert.Old.Id, + Version = insert.Old.Version, + ActionType = "delete", + Timestamp = DateTimeOffset.UtcNow + }) + .Insert(insert => new Outbox() + { + Key = "Channel-" + insert.New.Id, + Version = insert.New.Version, + ActionType = "update", + Timestamp = DateTimeOffset.UtcNow + }))) + .AfterInsert(trigger => trigger + .Action(action => action + .Insert(insert => new Outbox() + { + Key = "Channel-" + insert.New.Id, + Version = insert.New.Version, + ActionType = "insert", + Timestamp = DateTimeOffset.UtcNow + }))).AfterDelete(trigger => trigger + .Action(action => action + .Insert(insert => new Outbox() + { + Key = "Channel-" + insert.Old.Id, + Version = insert.Old.Version, + ActionType = "delete", + Timestamp = DateTimeOffset.UtcNow + }))); + base.OnModelCreating(modelBuilder); } } -public static class ValueConversionExtensions +public class Outbox +{ + public DateTimeOffset Timestamp { get; set; } + public string ActionType { get; set; } + public string Key { get; set; } + public ulong Version { get; set; } +} + +public class OutboxProcessor : IHostedService { - public static PropertyBuilder HasJsonConversion(this PropertyBuilder propertyBuilder) where T : class, new() + private readonly IDbContextFactory _dbContextFactory; + + public OutboxProcessor(IDbContextFactory dbContextFactory) + { + _dbContextFactory = dbContextFactory; + } + + private async Task ProcessOutbox(CancellationToken cancellationToken = default) + { + await using var db = + new AppDbContext(new DbContextOptionsBuilder().UseSqlite("Data Source=outbox.db").Options); + var outbox = db.Set(); + 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) { - var converter = new ValueConverter - ( - v => JsonSerializer.Serialize(v, JsonSerializerOptions.Default), - v => JsonSerializer.Deserialize(v, JsonSerializerOptions.Default) ?? new T() - ); - - var comparer = new ValueComparer - ( - (l, r) => JsonSerializer.Serialize(l,JsonSerializerOptions.Default) == JsonSerializer.Serialize(r,JsonSerializerOptions.Default), - v => v == null ? 0 : JsonConvert.SerializeObject(v).GetHashCode(), - v => JsonSerializer.Deserialize(JsonSerializer.Serialize(v,JsonSerializerOptions.Default), JsonSerializerOptions.Default)! - ); - - propertyBuilder.HasConversion(converter); - propertyBuilder.Metadata.SetValueConverter(converter); - propertyBuilder.Metadata.SetValueComparer(comparer); - propertyBuilder.HasColumnType("jsonb"); - - return propertyBuilder; + throw new NotImplementedException(); } } \ No newline at end of file diff --git a/BTCPayApp.Core/Data/AppLightningPayment.cs b/BTCPayApp.Core/Data/AppLightningPayment.cs new file mode 100644 index 0000000..c83563f --- /dev/null +++ b/BTCPayApp.Core/Data/AppLightningPayment.cs @@ -0,0 +1,37 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using BTCPayApp.Core.JsonConverters; +using BTCPayServer.Lightning; +using NBitcoin; + +namespace BTCPayApp.Core.Data; + +public class AppLightningPayment : VersionedData +{ + [JsonConverter(typeof(UInt256JsonConverter))] + public uint256 PaymentHash { get; set; } + + public string PaymentId { get; set; } + public string? Preimage { get; set; } + + [JsonConverter(typeof(UInt256JsonConverter))] + public uint256 Secret { get; set; } + + public bool Inbound { get; set; } + + [JsonConverter(typeof(DateTimeToUnixTimeConverter))] + public DateTimeOffset Timestamp { get; set; } + + [JsonConverter(typeof(LightMoneyJsonConverter))] + public LightMoney Value { get; set; } + + [JsonConverter(typeof(JsonStringEnumConverter))] + public LightningPaymentStatus Status { get; set; } + + [JsonConverter(typeof(BOLT11PaymentRequestJsonConverter))] + public BOLT11PaymentRequest PaymentRequest { get; set; } + + [JsonExtensionData] public Dictionary AdditionalData { get; set; } = new(); + + public override string Entity => "LightningPayment"; +} \ No newline at end of file diff --git a/BTCPayApp.Core/Data/Channel.cs b/BTCPayApp.Core/Data/Channel.cs index 0ea36c8..a376d1b 100644 --- a/BTCPayApp.Core/Data/Channel.cs +++ b/BTCPayApp.Core/Data/Channel.cs @@ -1,10 +1,11 @@ namespace BTCPayApp.Core.Data; -public class Channel +public class Channel:VersionedData { public string Id { get; set; } public List Aliases { get; set; } public byte[] Data { get; set; } - - + + + public override string Entity => "Channel"; } diff --git a/BTCPayApp.Core/Data/LightningConfig.cs b/BTCPayApp.Core/Data/LightningConfig.cs index 50776e5..faa7093 100644 --- a/BTCPayApp.Core/Data/LightningConfig.cs +++ b/BTCPayApp.Core/Data/LightningConfig.cs @@ -42,11 +42,4 @@ public byte[] RGB public Dictionary Peers { get; set; } = new(); public bool AcceptInboundConnection{ get; set; } -} - -public record PeerInfo -{ - public string Endpoint { get; set; } - public bool Persistent { get; set; } - public bool Trusted { get; set; } } \ No newline at end of file diff --git a/BTCPayApp.Core/Data/PeerInfo.cs b/BTCPayApp.Core/Data/PeerInfo.cs new file mode 100644 index 0000000..279cd5c --- /dev/null +++ b/BTCPayApp.Core/Data/PeerInfo.cs @@ -0,0 +1,8 @@ +namespace BTCPayApp.Core.Data; + +public record PeerInfo +{ + public string Endpoint { get; set; } + public bool Persistent { get; set; } + public bool Trusted { get; set; } +} \ No newline at end of file diff --git a/BTCPayApp.Core/Data/Setting.cs b/BTCPayApp.Core/Data/Setting.cs index e267596..29b802a 100644 --- a/BTCPayApp.Core/Data/Setting.cs +++ b/BTCPayApp.Core/Data/Setting.cs @@ -2,9 +2,11 @@ namespace BTCPayApp.Core.Data; -public class Setting +public class Setting:VersionedData { [Key] public string Key { get; set; } public byte[] Value { get; set; } + + public override string Entity => "Setting"; } diff --git a/BTCPayApp.Core/Data/ValueConversionExtensions.cs b/BTCPayApp.Core/Data/ValueConversionExtensions.cs new file mode 100644 index 0000000..46859b1 --- /dev/null +++ b/BTCPayApp.Core/Data/ValueConversionExtensions.cs @@ -0,0 +1,36 @@ +using System.Text.Json; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.ChangeTracking; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace BTCPayApp.Core.Data; + +public static class ValueConversionExtensions +{ + public static PropertyBuilder HasJsonConversion(this PropertyBuilder propertyBuilder) + where T : class, new() + { + var converter = new ValueConverter + ( + v => JsonSerializer.Serialize(v, JsonSerializerOptions.Default), + v => JsonSerializer.Deserialize(v, JsonSerializerOptions.Default) ?? new T() + ); + + var comparer = new ValueComparer + ( + (l, r) => JsonSerializer.Serialize(l, JsonSerializerOptions.Default) == + JsonSerializer.Serialize(r, JsonSerializerOptions.Default), + v => v == null ? 0 : JsonSerializer.Serialize(v, JsonSerializerOptions.Default).GetHashCode(), + v => JsonSerializer.Deserialize(JsonSerializer.Serialize(v, JsonSerializerOptions.Default), + JsonSerializerOptions.Default)! + ); + + propertyBuilder.HasConversion(converter); + propertyBuilder.Metadata.SetValueConverter(converter); + propertyBuilder.Metadata.SetValueComparer(comparer); + propertyBuilder.HasColumnType("jsonb"); + + return propertyBuilder; + } +} \ No newline at end of file diff --git a/BTCPayApp.Core/Data/VersionedData.cs b/BTCPayApp.Core/Data/VersionedData.cs new file mode 100644 index 0000000..3c6c264 --- /dev/null +++ b/BTCPayApp.Core/Data/VersionedData.cs @@ -0,0 +1,10 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace BTCPayApp.Core.Data; + +public abstract class VersionedData +{ + public ulong Version { get; set; } = 0; + [NotMapped] + public abstract string Entity { get; } +} \ No newline at end of file diff --git a/BTCPayApp.Core/Helpers/ChannelExtensions.cs b/BTCPayApp.Core/Helpers/ChannelExtensions.cs index 2611116..653459f 100644 --- a/BTCPayApp.Core/Helpers/ChannelExtensions.cs +++ b/BTCPayApp.Core/Helpers/ChannelExtensions.cs @@ -23,17 +23,17 @@ async Task OnEvent(object? sender, TEvent evt) await channel.Writer.WriteAsync(evt, cancellationToken); } - add(new AsyncEventHandler(OnEvent)); - _ = ProcessChannel(channel, processor, cancellationToken); + add(OnEvent); + _ = channel.ProcessChannel(processor, cancellationToken); return new DisposableWrapper(async () => { - remove(new AsyncEventHandler(OnEvent)); + remove(OnEvent); channel.Writer.Complete(); }); } - private static async Task ProcessChannel(Channel channel, Func processor, CancellationToken cancellationToken) + public static async Task ProcessChannel(this Channel channel, Func processor, CancellationToken cancellationToken) { while (await channel.Reader.WaitToReadAsync(cancellationToken)) { diff --git a/BTCPayApp.Core/LDK/PaymentsManager.cs b/BTCPayApp.Core/LDK/PaymentsManager.cs index c7875a3..9ee1e7f 100644 --- a/BTCPayApp.Core/LDK/PaymentsManager.cs +++ b/BTCPayApp.Core/LDK/PaymentsManager.cs @@ -1,45 +1,15 @@ using System.Collections.Concurrent; -using System.Security.Cryptography; using System.Text.Json; -using System.Text.Json.Serialization; +using BTCPayApp.Core; using BTCPayApp.Core.Attempt2; using BTCPayApp.Core.Data; using BTCPayApp.Core.Helpers; -using BTCPayApp.Core.JsonConverters; +using BTCPayApp.Core.LDK; using BTCPayApp.Core.LSP.JIT; using BTCPayServer.Lightning; using Microsoft.EntityFrameworkCore; using NBitcoin; using org.ldk.structs; -using org.ldk.util; - - -namespace BTCPayApp.Core.LDK; -public partial class AppLightningPayment -{ - - [JsonConverter(typeof(UInt256JsonConverter))] - public uint256 PaymentHash { get; set; } - public string PaymentId { get; set; } - public string? Preimage { get; set; } - - [JsonConverter(typeof(UInt256JsonConverter))] - public uint256 Secret { get; set; } - public bool Inbound { get; set; } - [JsonConverter(typeof(DateTimeToUnixTimeConverter))] - public DateTimeOffset Timestamp { get; set; } - - [JsonConverter(typeof(LightMoneyJsonConverter))] - public LightMoney Value { get; set; } - [JsonConverter(typeof(JsonStringEnumConverter))] - public LightningPaymentStatus Status { get; set; } - [JsonConverter(typeof(BOLT11PaymentRequestJsonConverter))] - public BOLT11PaymentRequest PaymentRequest { get; set; } - - [JsonExtensionData] public Dictionary AdditionalData { get; set; } = new(); - -} - public class PaymentsManager : diff --git a/BTCPayApp.Core/LSP/JIT/IJITService.cs b/BTCPayApp.Core/LSP/JIT/IJITService.cs index 136bb32..97397bd 100644 --- a/BTCPayApp.Core/LSP/JIT/IJITService.cs +++ b/BTCPayApp.Core/LSP/JIT/IJITService.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; +using BTCPayApp.Core.Data; using BTCPayApp.Core.JsonConverters; -using BTCPayApp.Core.LDK; using BTCPayServer.Lightning; namespace BTCPayApp.Core.LSP.JIT; diff --git a/BTCPayApp.Core/StartupExtensions.cs b/BTCPayApp.Core/StartupExtensions.cs index ebfd39a..7891d57 100644 --- a/BTCPayApp.Core/StartupExtensions.cs +++ b/BTCPayApp.Core/StartupExtensions.cs @@ -4,6 +4,7 @@ using BTCPayApp.Core.Contracts; using BTCPayApp.Core.Data; using BTCPayApp.Core.LDK; +using Laraue.EfCoreTriggers.SqlLite.Extensions; using Microsoft.AspNetCore.Components.Authorization; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; @@ -20,6 +21,7 @@ public static IServiceCollection ConfigureBTCPayAppCore(this IServiceCollection { var dir = provider.GetRequiredService().GetAppDataDirectory().ConfigureAwait(false).GetAwaiter().GetResult(); options.UseSqlite($"Data Source={dir}/app.db"); + options.UseSqlLiteTriggers(); }); serviceCollection.AddHostedService(); serviceCollection.AddHttpClient(); From e02ca2ba7878c0bf11ff332d76a76ce358b8baf7 Mon Sep 17 00:00:00 2001 From: Kukks Date: Wed, 10 Jul 2024 22:33:59 +0200 Subject: [PATCH 07/14] WIP (submodule not updated) --- .../Attempt2/BTCPayAppServerClient.cs | 42 +-- .../Attempt2/BTCPayConnectionManager.cs | 4 +- BTCPayApp.Core/Data/AppDbContext.cs | 24 +- BTCPayApp.Core/Data/EFExtensions.cs | 22 ++ BTCPayApp.Core/Data/Setting.cs | 1 + BTCPayApp.Core/DatabaseConfigProvider.cs | 180 ++++++------- BTCPayApp.Core/LDK/PaymentsManager.cs | 12 + BTCPayApp.Tests/BTCPayAppTestServer.cs | 32 +-- .../WebApplicationFactoryExtensions.cs | 7 +- .../Settings/LightningChannelsPage.razor | 4 +- .../Pages/Settings/WithdrawPage.razor | 246 ++++++++---------- BTCPayApp.UI/StateMiddleware.cs | 2 +- submodules/btcpayserver | 2 +- 13 files changed, 307 insertions(+), 271 deletions(-) create mode 100644 BTCPayApp.Core/Data/EFExtensions.cs diff --git a/BTCPayApp.Core/Attempt2/BTCPayAppServerClient.cs b/BTCPayApp.Core/Attempt2/BTCPayAppServerClient.cs index b1324a8..a38890c 100644 --- a/BTCPayApp.Core/Attempt2/BTCPayAppServerClient.cs +++ b/BTCPayApp.Core/Attempt2/BTCPayAppServerClient.cs @@ -1,5 +1,6 @@ using System.Text; using BTCPayApp.CommonServer; +using BTCPayApp.Core.Data; using BTCPayApp.Core.Helpers; using BTCPayApp.Core.LDK; using BTCPayServer.Client.Models; @@ -53,46 +54,49 @@ public static async Task> ToInvoices(this Task logger, IServiceProvider serviceProvider) : IBTCPayAppHubClient +public class BTCPayAppServerClient : IBTCPayAppHubClient { - public event AsyncEventHandler? OnNewBlock; - public event AsyncEventHandler? OnTransactionDetected; - public event AsyncEventHandler? OnNotifyNetwork; - public event AsyncEventHandler? OnServerNodeInfo; - public event AsyncEventHandler? OnNotifyServerEvent; + private readonly ILogger _logger; + private readonly IServiceProvider _serviceProvider; + + public BTCPayAppServerClient(ILogger logger, IServiceProvider serviceProvider) + { + _logger = logger; + _serviceProvider = serviceProvider; + } - public async Task NotifyServerEvent(ServerEvent serverEvent) + public async Task NotifyServerEvent(IServerEvent serverEvent) { - logger.LogInformation("NotifyServerEvent: {Type} - {Details}", serverEvent.Type, serverEvent.ToString()); - await OnNotifyServerEvent?.Invoke(this, serverEvent.Type)!; + _logger.LogInformation("NotifyServerEvent: {ServerEventType}", serverEvent.Type); + await OnNotifyServerEvent?.Invoke(this, serverEvent)!; } public async Task NotifyNetwork(string network) { - logger.LogInformation("NotifyNetwork: {network}", network); + _logger.LogInformation("NotifyNetwork: {network}", network); await OnNotifyNetwork?.Invoke(this, network); } public async Task NotifyServerNode(string nodeInfo) { - logger.LogInformation("NotifyServerNode: {nodeInfo}", nodeInfo); + _logger.LogInformation("NotifyServerNode: {nodeInfo}", nodeInfo); await OnServerNodeInfo?.Invoke(this, nodeInfo); } public async Task TransactionDetected(TransactionDetectedRequest request) { - logger.LogInformation($"OnTransactionDetected: {request.TxId}"); + _logger.LogInformation($"OnTransactionDetected: {request.TxId}"); await OnTransactionDetected?.Invoke(this, request); } public async Task NewBlock(string block) { - logger.LogInformation("NewBlock: {block}", block); + _logger.LogInformation("NewBlock: {block}", block); await OnNewBlock?.Invoke(this, block); } private PaymentsManager PaymentsManager => - serviceProvider.GetRequiredService().Node.PaymentsManager; + _serviceProvider.GetRequiredService().Node.PaymentsManager; public async Task CreateInvoice(CreateLightningInvoiceRequest createLightningInvoiceRequest) { @@ -128,7 +132,7 @@ public async Task> GetLightningInvoices(ListInvoicesParam public async Task PayInvoice(string bolt11, long? amountMilliSatoshi) { - var network = serviceProvider.GetRequiredService().Network; + var network = _serviceProvider.GetRequiredService().Network; var bolt = BOLT11PaymentRequest.Parse(bolt11, network); try { @@ -153,8 +157,14 @@ public async Task PayInvoice(string bolt11, long? amountMilliSatosh } catch (Exception e) { - logger.LogError(e, "Error paying invoice"); + _logger.LogError(e, "Error paying invoice"); return new PayResponse(PayResult.Error, e.Message); } } + + public event AsyncEventHandler? OnNewBlock; + public event AsyncEventHandler? OnTransactionDetected; + public event AsyncEventHandler? OnNotifyNetwork; + public event AsyncEventHandler? OnServerNodeInfo; + public event AsyncEventHandler? OnNotifyServerEvent; } diff --git a/BTCPayApp.Core/Attempt2/BTCPayConnectionManager.cs b/BTCPayApp.Core/Attempt2/BTCPayConnectionManager.cs index 11de209..c0dc613 100644 --- a/BTCPayApp.Core/Attempt2/BTCPayConnectionManager.cs +++ b/BTCPayApp.Core/Attempt2/BTCPayConnectionManager.cs @@ -73,9 +73,9 @@ private async Task OnServerNodeInfo(object? sender, string e) ReportedNodeInfo = e; } - private async Task OnNotifyServerEvent(object? sender, string e) + private async Task OnNotifyServerEvent(object? sender, IServerEvent serverEvent) { - _logger.LogInformation("OnNotifyServerEvent: {ServerEventType}", e); + _logger.LogInformation("OnNotifyServerEvent: {ServerEventType}", serverEvent.Type); } private async Task OnNotifyNetwork(object? sender, string e) diff --git a/BTCPayApp.Core/Data/AppDbContext.cs b/BTCPayApp.Core/Data/AppDbContext.cs index bad6b1a..0b3a94c 100644 --- a/BTCPayApp.Core/Data/AppDbContext.cs +++ b/BTCPayApp.Core/Data/AppDbContext.cs @@ -51,7 +51,12 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) //handling versioned data - modelBuilder.Entity().AfterDelete(trigger => trigger.Action(group => group.Insert( + + + modelBuilder.Entity() + .AfterDelete(trigger => + trigger.Action(group => + group.Insert( @ref => new Outbox() { Version = @ref.Old.Version, @@ -59,6 +64,19 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) ActionType = "delete" }))); + modelBuilder.Entity() + .AfterDelete(trigger => trigger.Action(group => + group + .Condition(@ref => @ref.Old.Backup) + .Insert( + @ref => new Outbox() + { + Version = @ref.Old.Version, + Key = "Setting-" + @ref.Old.Key, + ActionType = "delete" + }))); + + foreach (var entityType in modelBuilder.Model.GetEntityTypes()) { if(typeof(VersionedData).IsAssignableFrom(entityType.ClrType)) @@ -119,8 +137,12 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) base.OnModelCreating(modelBuilder); } + } + + + public class Outbox { public DateTimeOffset Timestamp { get; set; } diff --git a/BTCPayApp.Core/Data/EFExtensions.cs b/BTCPayApp.Core/Data/EFExtensions.cs new file mode 100644 index 0000000..7ffa930 --- /dev/null +++ b/BTCPayApp.Core/Data/EFExtensions.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; + +namespace BTCPayApp.Core.Data; + +public static class EFExtensions +{ + + public static async Task CrappyUpsert(this DbContext ctx, T item, CancellationToken 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); + } + } +} \ No newline at end of file diff --git a/BTCPayApp.Core/Data/Setting.cs b/BTCPayApp.Core/Data/Setting.cs index 29b802a..fb86aba 100644 --- a/BTCPayApp.Core/Data/Setting.cs +++ b/BTCPayApp.Core/Data/Setting.cs @@ -7,6 +7,7 @@ public class Setting:VersionedData [Key] public string Key { get; set; } public byte[] Value { get; set; } + public bool Backup { get; set; } = true; public override string Entity => "Setting"; } diff --git a/BTCPayApp.Core/DatabaseConfigProvider.cs b/BTCPayApp.Core/DatabaseConfigProvider.cs index 124de2c..fd88a80 100644 --- a/BTCPayApp.Core/DatabaseConfigProvider.cs +++ b/BTCPayApp.Core/DatabaseConfigProvider.cs @@ -1,103 +1,91 @@ -using System.Collections.Concurrent; +// using System.Collections.Concurrent; +// using System.Text.Json; +// using BTCPayApp.Core.Attempt2; +// using BTCPayApp.Core.Contracts; +// using BTCPayApp.Core.Data; +// using BTCPayApp.VSS; +// using BTCPayServer.Lightning; +// using Microsoft.EntityFrameworkCore; +// using Microsoft.EntityFrameworkCore.Diagnostics; +// using Microsoft.Extensions.Logging; +// using VSSProto; +// +// namespace BTCPayApp.Core; +// +// +// +// public class VSSMapperInterceptor : SaveChangesInterceptor +// { +// +// public VSSMapperInterceptor(BTCPayConnectionManager btcPayConnectionManager, ILogger logger) +// { +// } +// +// private ConcurrentDictionary PendingEvents = new ConcurrentDictionary(); +// public override ValueTask SavedChangesAsync(SaveChangesCompletedEventData eventData, int result, +// CancellationToken cancellationToken = new CancellationToken()) +// { +// return base.SavedChangesAsync(eventData, result, cancellationToken); +// } +// +// private IVSSAPI api; +// public override ValueTask> SavingChangesAsync(DbContextEventData eventData, InterceptionResult result, +// CancellationToken cancellationToken = new CancellationToken()) +// { +// foreach (var entry in eventData.Context.ChangeTracker.Entries()) +// { +// +// if (entry.Entity is LightningPayment lightningPayment) +// { +// if (entry.State == EntityState.Deleted) +// { +// +// api.DeleteObjectAsync(new DeleteObjectRequest +// { +// KeyValue = new KeyValue() +// { +// +// } +// Key = $"LightningPayment/{lightningPayment.Id}" +// }); +// } +// } +// if (entry.Entity is Channel channel) +// { +// +// } +// if (entry.Entity is Setting setting) +// { +// +// } +// } +// +// return base.SavingChangesAsync(eventData, result, cancellationToken); +// } +// +// public override Task SaveChangesCanceledAsync(DbContextEventData eventData, +// CancellationToken cancellationToken = new CancellationToken()) +// { +// PendingEvents.Remove(eventData.EventId, out _); +// return base.SaveChangesCanceledAsync(eventData, cancellationToken); +// } +// +// public override Task SaveChangesFailedAsync(DbContextErrorEventData eventData, +// CancellationToken cancellationToken = new CancellationToken()) +// { +// PendingEvents.Remove(eventData.EventId, out _); +// return base.SaveChangesFailedAsync(eventData, cancellationToken); +// } +// +// +// } +// + + using System.Text.Json; -using BTCPayApp.Core.Attempt2; using BTCPayApp.Core.Contracts; using BTCPayApp.Core.Data; -using BTCPayApp.VSS; -using BTCPayServer.Lightning; using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.Extensions.Logging; -using VSSProto; - -namespace BTCPayApp.Core; - - - -public class VSSMapperInterceptor : SaveChangesInterceptor -{ - - public VSSMapperInterceptor(BTCPayConnectionManager btcPayConnectionManager, ILogger logger) - { - } - - private ConcurrentDictionary PendingEvents = new ConcurrentDictionary(); - public override ValueTask SavedChangesAsync(SaveChangesCompletedEventData eventData, int result, - CancellationToken cancellationToken = new CancellationToken()) - { - return base.SavedChangesAsync(eventData, result, cancellationToken); - } - - private IVSSAPI api; - public override ValueTask> SavingChangesAsync(DbContextEventData eventData, InterceptionResult result, - CancellationToken cancellationToken = new CancellationToken()) - { - foreach (var entry in eventData.Context.ChangeTracker.Entries()) - { - - if (entry.Entity is LightningPayment lightningPayment) - { - if (entry.State == EntityState.Deleted) - { - - api.DeleteObjectAsync(new DeleteObjectRequest - { - KeyValue = new KeyValue() - { - - } - Key = $"LightningPayment/{lightningPayment.Id}" - }); - } - } - if (entry.Entity is Channel channel) - { - - } - if (entry.Entity is Setting setting) - { - - } - } - - return base.SavingChangesAsync(eventData, result, cancellationToken); - } - - public override Task SaveChangesCanceledAsync(DbContextEventData eventData, - CancellationToken cancellationToken = new CancellationToken()) - { - PendingEvents.Remove(eventData.EventId, out _); - return base.SaveChangesCanceledAsync(eventData, cancellationToken); - } - - public override Task SaveChangesFailedAsync(DbContextErrorEventData eventData, - CancellationToken cancellationToken = new CancellationToken()) - { - PendingEvents.Remove(eventData.EventId, out _); - return base.SaveChangesFailedAsync(eventData, cancellationToken); - } - - -} - -public static class EFExtensions -{ - - public static async Task CrappyUpsert(this DbContext ctx, T item, CancellationToken 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); - } - } -} public class DatabaseConfigProvider: IConfigProvider { diff --git a/BTCPayApp.Core/LDK/PaymentsManager.cs b/BTCPayApp.Core/LDK/PaymentsManager.cs index 9ee1e7f..8633d5b 100644 --- a/BTCPayApp.Core/LDK/PaymentsManager.cs +++ b/BTCPayApp.Core/LDK/PaymentsManager.cs @@ -253,6 +253,18 @@ public async Task PayInvoice(BOLT11PaymentRequest paymentRe return outbound; } + public async Task Cancel(AppLightningPayment lightningPayment) + { + if (lightningPayment.Inbound) + { + await CancelInbound(lightningPayment.PaymentHash); + } + else + { + await CancelOutbound(lightningPayment.PaymentId); + } + } + public async Task CancelInbound(uint256 paymentHash) { diff --git a/BTCPayApp.Tests/BTCPayAppTestServer.cs b/BTCPayApp.Tests/BTCPayAppTestServer.cs index 287dd04..7c4adf7 100644 --- a/BTCPayApp.Tests/BTCPayAppTestServer.cs +++ b/BTCPayApp.Tests/BTCPayAppTestServer.cs @@ -19,23 +19,23 @@ public BTCPayAppTestServer(ITestOutputHelper output, bool newDir = true, Diction { if (newDir) { - _config.AddOrReplace("BTCPAYAPP_DIRNAME", "btcpayserver-test-" + RandomUtils.GetUInt32()); + Config.AddOrReplace("BTCPAYAPP_DIRNAME", "btcpayserver-test-" + RandomUtils.GetUInt32()); } } } class BaseWebApplicationFactory : WebApplicationFactory where T : class { - protected IHost? _host; - protected readonly ITestOutputHelper _output; - protected readonly Dictionary _config; - protected readonly Task _playwrightInstallTask; + protected IHost? Host; + protected readonly ITestOutputHelper Output; + protected readonly Dictionary Config; + protected readonly Task PlaywrightInstallTask; public string ServerAddress { get { - if (_host is null) + if (Host is null) { CreateDefaultClient(); } @@ -46,12 +46,12 @@ public string ServerAddress public BaseWebApplicationFactory(ITestOutputHelper output, Dictionary? config = null) { - _output = output; + Output = output; - _config = config ?? new(); + Config = config ?? new(); - _playwrightInstallTask ??= Task.Run(InstallPlaywright); + PlaywrightInstallTask ??= Task.Run(InstallPlaywright); } public class LifetimeBridge @@ -76,9 +76,9 @@ protected override IHost CreateHost(IHostBuilder builder) TaskCompletionSource tcs = new TaskCompletionSource(); builder.ConfigureWebHost(webHostBuilder => webHostBuilder.UseKestrel().UseUrls("https://127.0.0.1:0").ConfigureServices(collection => collection.AddSingleton(provider => new LifetimeBridge(provider.GetRequiredService(), provider.GetRequiredService(), tcs)))); // configure and start the actual host using Kestrel. - _host = builder.Build(); - _host.Start(); - _host.Services.GetRequiredService(); + Host = builder.Build(); + Host.Start(); + Host.Services.GetRequiredService(); // Extract the selected dynamic port out of the Kestrel server // and assign it onto the client options for convenience so it // "just works" as otherwise it'll be the default http://localhost @@ -96,7 +96,7 @@ protected override void ConfigureWebHost(IWebHostBuilder builder) base.ConfigureWebHost(builder); builder .ConfigureAppConfiguration(configurationBuilder => - configurationBuilder.AddInMemoryCollection(_config)) + configurationBuilder.AddInMemoryCollection(Config!)) .ConfigureLogging( logging => { @@ -104,8 +104,8 @@ protected override void ConfigureWebHost(IWebHostBuilder builder) var useScopes = logging.UsesScopes(); // remove other logging providers, such as remote loggers or unnecessary event logs logging.ClearProviders(); - logging.Services.AddSingleton(r => - new WebApplicationFactoryExtensions.XunitLoggerProvider(_output, useScopes)); + logging.Services.AddSingleton(_ => + new WebApplicationFactoryExtensions.XunitLoggerProvider(Output, useScopes)); }); } @@ -117,7 +117,7 @@ private static void InstallPlaywright() public async Task InitializeAsync() { Assert.NotNull(ServerAddress); - await _playwrightInstallTask; + await PlaywrightInstallTask; Playwright = await Microsoft.Playwright.Playwright.CreateAsync(); Browser = await Playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions { diff --git a/BTCPayApp.Tests/WebApplicationFactoryExtensions.cs b/BTCPayApp.Tests/WebApplicationFactoryExtensions.cs index 31e8eaa..ec09926 100644 --- a/BTCPayApp.Tests/WebApplicationFactoryExtensions.cs +++ b/BTCPayApp.Tests/WebApplicationFactoryExtensions.cs @@ -161,11 +161,8 @@ public static bool UsesScopes(this ILoggingBuilder builder) if (logging == default) return false; - var includeScopes = logging?.GetValue("Console:IncludeScopes", false); - if (!includeScopes.Value) - includeScopes = logging?.GetValue("IncludeScopes", false); - - return includeScopes.GetValueOrDefault(false); + var includeScopes = logging.GetValue("Console:IncludeScopes", false) || logging.GetValue("IncludeScopes", false); + return includeScopes; } #endregion diff --git a/BTCPayApp.UI/Pages/Settings/LightningChannelsPage.razor b/BTCPayApp.UI/Pages/Settings/LightningChannelsPage.razor index 4369882..6c0f750 100644 --- a/BTCPayApp.UI/Pages/Settings/LightningChannelsPage.razor +++ b/BTCPayApp.UI/Pages/Settings/LightningChannelsPage.razor @@ -263,7 +263,7 @@ } } - private async void UpdatePeer(string toString, PeerInfo? value) + private async void UpdatePeer(string pubKeyHex, PeerInfo? value) { try { @@ -271,7 +271,7 @@ await InvokeAsync(StateHasChanged); await _semaphore.WaitAsync(); - await Node.Peer(toString, value); + await Node.Peer(new PubKey(pubKeyHex), value); } finally { diff --git a/BTCPayApp.UI/Pages/Settings/WithdrawPage.razor b/BTCPayApp.UI/Pages/Settings/WithdrawPage.razor index b55ca34..b2a794b 100644 --- a/BTCPayApp.UI/Pages/Settings/WithdrawPage.razor +++ b/BTCPayApp.UI/Pages/Settings/WithdrawPage.razor @@ -3,10 +3,9 @@ @using BTCPayApp.UI.Features @using BTCPayApp.Core.Attempt2 @using BTCPayApp.UI.Components.Layout -@using BTCPayServer.Lightning @using NBitcoin -@using LightningPayment = BTCPayApp.CommonServer.Models.LightningPayment @using BTCPayApp.Core.Data +@using BTCPayServer.Lightning @using NBitcoin.Crypto @inject LightningNodeManager LightningNodeManager @inject IState State @@ -22,88 +21,87 @@ @if (State.Value.LightningNodeState is LightningNodeState.Loaded) {
-

Payments

- @if (_payments?.Any() is true) - { -
- - - - - - - - - - - - - - - - - @foreach (var payment in _payments) - { - - - - - - - - - - - - - } - -
Payment HashInboundIdPreimageSecretTimestampValueStatusInvoices
- @if (payment.Status == LightningPaymentStatus.Pending) - { - - } - - @payment.PaymentHash@payment.Inbound@payment.PaymentId@payment.Preimage@payment.Secret@payment.Timestamp@payment.Value@payment.Status@string.Join('\n', payment.PaymentRequests)
-
- } - else - { -

There are no payments, yet.

- } +

Payments

+ @if (_payments?.Any() is true) + { +
+ + + + + + + + + + + + + + + + + @foreach (var payment in _payments) + { + + + + + + + + + + + + + } + +
Payment HashInboundIdPreimageSecretTimestampValueStatusInvoices
+ @if (payment.Status == LightningPaymentStatus.Pending) + { + + } + + @payment.PaymentHash@payment.Inbound@payment.PaymentId@payment.Preimage@payment.Secret@payment.Timestamp@payment.Value@payment.Status@payment.PaymentRequest
+
+ } + else + { +

There are no payments, yet.

+ } -
+
-
- - -
+
+ + +
-
- - - -
+
+ + + +
- @if (paymentResponse is not null) - { -

@paymentResponse

- } -
-
- - } else if (Loading) + @if (paymentResponse is not null) + { +

@paymentResponse

+ } + + + + } + else if (Loading) { loading... } @code { - private string _nodeId; - private LightningConfig? _config; - private List? _payments; + private List? _payments; private LDKNode? Node => LightningNodeManager.Node; private decimal? paymentRequestAmt; private string? paymentRequestSend; @@ -119,77 +117,54 @@ private async Task FetchData() { - Loading = true; - await InvokeAsync(StateHasChanged); - - try - { - await _semaphore.WaitAsync(); - _config = await Node.GetConfig(); - _nodeId = Node.NodeId.ToString(); - _payments = await LightningNodeManager.Node?.PaymentsManager.List(payments => payments); - } - finally + await Wrap(async () => { - Loading = false; - await InvokeAsync(StateHasChanged); - _semaphore.Release(); - } + if (Node is null) return; + _payments = await Node.PaymentsManager.List(payments => payments); + }); } private async void ReceivePayment() { - if (Loading || paymentRequestAmt is null) return; - try + if (paymentRequestSend is null) return; + await Wrap(async () => { - Loading = true; - await InvokeAsync(StateHasChanged); - await _semaphore.WaitAsync(); - var hash = new uint256(Hashes.SHA256(RandomUtils.GetBytes(32))); - var result = await Node.PaymentsManager.RequestPayment(LightMoney.Satoshis(paymentRequestAmt??0), TimeSpan.FromDays(1), hash); + try + { + var hash = new uint256(Hashes.SHA256(RandomUtils.GetBytes(32))); + var result = await Node.PaymentsManager.RequestPayment(LightMoney.Satoshis(paymentRequestAmt ?? 0), TimeSpan.FromDays(1), hash); - paymentResponse = $"Payment request created with invs {string.Join(',',result.PaymentRequests)}"; - paymentRequestAmt = null; - } - catch (Exception e) - { - paymentResponse = $"Error: {e.Message}"; - } - finally - { - Loading = false; - await InvokeAsync(StateHasChanged); - _semaphore.Release(); - } + paymentResponse = $"Payment request created: {result.PaymentRequest}"; + paymentRequestAmt = null; + } + catch (Exception e) + { + paymentResponse = $"Error: {e.Message}"; + } + }); } private async void SendPayment() { - if (Loading || paymentRequestSend is null) return; - try - { - Loading = true; - await InvokeAsync(StateHasChanged); - await _semaphore.WaitAsync(); - var invoice = BOLT11PaymentRequest.Parse(paymentRequestSend, Node.Network ); - var result = await Node.PaymentsManager.PayInvoice(invoice, paymentRequestAmt is null? null: LightMoney.Satoshis((long)paymentRequestAmt.Value)); - paymentResponse = $"Payment {result.PaymentId} sent with status {result.Status}"; - paymentRequestAmt = null; - paymentRequestSend = null; - } - catch (Exception e) + if (paymentRequestSend is null) return; + await Wrap(async () => { - paymentResponse = $"Error: {e.Message}"; - } - finally - { - Loading = false; - await InvokeAsync(StateHasChanged); - _semaphore.Release(); - } + try + { + var invoice = BOLT11PaymentRequest.Parse(paymentRequestSend, Node.Network); + var result = await Node.PaymentsManager.PayInvoice(invoice, paymentRequestAmt is null ? null : LightMoney.Satoshis((long) paymentRequestAmt.Value)); + paymentResponse = $"Payment {result.PaymentId} sent with status {result.Status}"; + paymentRequestAmt = null; + paymentRequestSend = null; + } + catch (Exception e) + { + paymentResponse = $"Error: {e.Message}"; + } + }); } - private async void Cancel(string paymentId, bool inb) + private async Task Wrap(Func action) { if (Loading) return; try @@ -197,7 +172,7 @@ Loading = true; await InvokeAsync(StateHasChanged); await _semaphore.WaitAsync(); - await Node.PaymentsManager.Cancel(paymentId, inb); + await action(); } finally { @@ -206,4 +181,13 @@ _semaphore.Release(); } } -} + + private async Task Cancel(AppLightningPayment payment) + { + await Wrap(async () => + { + await Node.PaymentsManager.Cancel(payment); + }); + } + +} \ No newline at end of file diff --git a/BTCPayApp.UI/StateMiddleware.cs b/BTCPayApp.UI/StateMiddleware.cs index af82669..ea74d29 100644 --- a/BTCPayApp.UI/StateMiddleware.cs +++ b/BTCPayApp.UI/StateMiddleware.cs @@ -68,7 +68,7 @@ private void ListenIn(IDispatcher dispatcher) btcpayAppServerClient.OnNotifyServerEvent += (sender, serverEvent) => { Logger.LogDebug("Received Server Event: {ServerEventType}", serverEvent); - switch (serverEvent) + switch (serverEvent.Type.ToLowerInvariant()) { case "notifications-updated": dispatcher.Dispatch(new NotificationState.FetchNotifications()); diff --git a/submodules/btcpayserver b/submodules/btcpayserver index 6ce6e1a..63cd54d 160000 --- a/submodules/btcpayserver +++ b/submodules/btcpayserver @@ -1 +1 @@ -Subproject commit 6ce6e1aae3e256641470ca3f3d74947861853bc2 +Subproject commit 63cd54d6fc4c595b96bf2c9b4795c9c157dd1a93 From f7fe33a730c4d5f281eaecf92926c4a840c8526d Mon Sep 17 00:00:00 2001 From: Kukks Date: Mon, 22 Jul 2024 11:54:06 +0200 Subject: [PATCH 08/14] separate file --- BTCPayApp.Core/Attempt2/AppToServerHelper.cs | 46 +++++++++++++++++++ .../Attempt2/BTCPayAppServerClient.cs | 43 ----------------- 2 files changed, 46 insertions(+), 43 deletions(-) create mode 100644 BTCPayApp.Core/Attempt2/AppToServerHelper.cs diff --git a/BTCPayApp.Core/Attempt2/AppToServerHelper.cs b/BTCPayApp.Core/Attempt2/AppToServerHelper.cs new file mode 100644 index 0000000..44b25ad --- /dev/null +++ b/BTCPayApp.Core/Attempt2/AppToServerHelper.cs @@ -0,0 +1,46 @@ +using BTCPayApp.Core.Data; +using BTCPayServer.Lightning; + +namespace BTCPayApp.Core.Attempt2; + +public static class AppToServerHelper +{ + + public static LightningInvoice ToInvoice(this AppLightningPayment lightningPayment) + { + return new LightningInvoice() + { + Id = lightningPayment.PaymentHash.ToString(), + Amount = lightningPayment.Value, + PaymentHash = lightningPayment.PaymentHash.ToString(), + Preimage = lightningPayment.Preimage, + PaidAt = lightningPayment.Status == LightningPaymentStatus.Complete? DateTimeOffset.UtcNow: null, //TODO: store these in ln payment + BOLT11 = lightningPayment.PaymentRequest.ToString(), + Status = lightningPayment.Status == LightningPaymentStatus.Complete? LightningInvoiceStatus.Paid: lightningPayment.PaymentRequest.ExpiryDate < DateTimeOffset.UtcNow? LightningInvoiceStatus.Expired: LightningInvoiceStatus.Unpaid + }; + } + + public static LightningPayment ToPayment(this AppLightningPayment lightningPayment) + { + return new LightningPayment() + { + Id = lightningPayment.PaymentHash.ToString(), + Amount = LightMoney.MilliSatoshis(lightningPayment.Value), + PaymentHash = lightningPayment.PaymentHash.ToString(), + Preimage = lightningPayment.Preimage, + BOLT11 = lightningPayment.PaymentRequest.ToString(), + Status = lightningPayment.Status + }; + } + + public static async Task> ToPayments(this Task> appLightningPayments) + { + var result = await appLightningPayments; + return result.Select(ToPayment).ToList(); + } + public static async Task> ToInvoices(this Task> appLightningPayments) + { + var result = await appLightningPayments; + return result.Select(ToInvoice).ToList(); + } +} \ No newline at end of file diff --git a/BTCPayApp.Core/Attempt2/BTCPayAppServerClient.cs b/BTCPayApp.Core/Attempt2/BTCPayAppServerClient.cs index a38890c..3182465 100644 --- a/BTCPayApp.Core/Attempt2/BTCPayAppServerClient.cs +++ b/BTCPayApp.Core/Attempt2/BTCPayAppServerClient.cs @@ -1,6 +1,5 @@ using System.Text; using BTCPayApp.CommonServer; -using BTCPayApp.Core.Data; using BTCPayApp.Core.Helpers; using BTCPayApp.Core.LDK; using BTCPayServer.Client.Models; @@ -12,48 +11,6 @@ namespace BTCPayApp.Core.Attempt2; -public static class AppToServerHelper -{ - - public static LightningInvoice ToInvoice(this AppLightningPayment lightningPayment) - { - return new LightningInvoice() - { - Id = lightningPayment.PaymentHash.ToString(), - Amount = lightningPayment.Value, - PaymentHash = lightningPayment.PaymentHash.ToString(), - Preimage = lightningPayment.Preimage, - PaidAt = lightningPayment.Status == LightningPaymentStatus.Complete? DateTimeOffset.UtcNow: null, //TODO: store these in ln payment - BOLT11 = lightningPayment.PaymentRequest.ToString(), - Status = lightningPayment.Status == LightningPaymentStatus.Complete? LightningInvoiceStatus.Paid: lightningPayment.PaymentRequest.ExpiryDate < DateTimeOffset.UtcNow? LightningInvoiceStatus.Expired: LightningInvoiceStatus.Unpaid - }; - } - - public static LightningPayment ToPayment(this AppLightningPayment lightningPayment) - { - return new LightningPayment() - { - Id = lightningPayment.PaymentHash.ToString(), - Amount = LightMoney.MilliSatoshis(lightningPayment.Value), - PaymentHash = lightningPayment.PaymentHash.ToString(), - Preimage = lightningPayment.Preimage, - BOLT11 = lightningPayment.PaymentRequest.ToString(), - Status = lightningPayment.Status - }; - } - - public static async Task> ToPayments(this Task> appLightningPayments) - { - var result = await appLightningPayments; - return result.Select(ToPayment).ToList(); - } - public static async Task> ToInvoices(this Task> appLightningPayments) - { - var result = await appLightningPayments; - return result.Select(ToInvoice).ToList(); - } -} - public class BTCPayAppServerClient : IBTCPayAppHubClient { private readonly ILogger _logger; From ec215938df647ca36fb6994129717e37dd4c43a3 Mon Sep 17 00:00:00 2001 From: Kukks Date: Tue, 23 Jul 2024 16:28:35 +0200 Subject: [PATCH 09/14] wip --- .../Attempt2/BTCPayAppServerClient.cs | 15 +- BTCPayApp.Core/Attempt2/LDKKVStore.cs | 27 ++- BTCPayApp.Core/Attempt2/LDKNode.cs | 31 ++- .../Attempt2/LightningNodeService.cs | 4 +- BTCPayApp.Core/BTCPayApp.Core.csproj | 2 + BTCPayApp.Core/Data/AppDbContext.cs | 221 +++++++++++------- BTCPayApp.Core/Data/AppLightningPayment.cs | 2 - BTCPayApp.Core/Data/Channel.cs | 11 +- .../Data/DesignTimeAppContextFactory.cs | 4 +- BTCPayApp.Core/Data/LightningConfig.cs | 2 +- BTCPayApp.Core/Data/Setting.cs | 2 - BTCPayApp.Core/Data/VersionedData.cs | 2 - BTCPayApp.Core/DatabaseConfigProvider.cs | 9 +- BTCPayApp.Core/LDK/LDKPersistInterface.cs | 58 ++++- .../20240723141157_triggers.Designer.cs | 201 ++++++++++++++++ .../Migrations/20240723141157_triggers.cs | 155 ++++++++++++ .../Migrations/AppDbContextModelSnapshot.cs | 164 ++++++++++--- submodules/btcpayserver | 2 +- 18 files changed, 752 insertions(+), 160 deletions(-) create mode 100644 BTCPayApp.Core/Migrations/20240723141157_triggers.Designer.cs create mode 100644 BTCPayApp.Core/Migrations/20240723141157_triggers.cs diff --git a/BTCPayApp.Core/Attempt2/BTCPayAppServerClient.cs b/BTCPayApp.Core/Attempt2/BTCPayAppServerClient.cs index fd71caa..bf9fa54 100644 --- a/BTCPayApp.Core/Attempt2/BTCPayAppServerClient.cs +++ b/BTCPayApp.Core/Attempt2/BTCPayAppServerClient.cs @@ -1,7 +1,6 @@ using System.Text; using BTCPayApp.CommonServer; using BTCPayApp.Core.Helpers; -using BTCPayApp.Core.LDK; using BTCPayServer.Client.Models; using BTCPayServer.Lightning; using Microsoft.Extensions.DependencyInjection; @@ -11,7 +10,7 @@ namespace BTCPayApp.Core.Attempt2; -public class BTCPayAppServerClient : IBTCPayAppHubClient +public class BTCPayAppServerClient(ILogger _logger, IServiceProvider _serviceProvider) : IBTCPayAppHubClient { public event AsyncEventHandler? OnNewBlock; public event AsyncEventHandler? OnTransactionDetected; @@ -19,10 +18,10 @@ public class BTCPayAppServerClient : IBTCPayAppHubClient public event AsyncEventHandler? OnServerNodeInfo; public event AsyncEventHandler? OnNotifyServerEvent; - public BTCPayAppServerClient(ILogger logger, IServiceProvider serviceProvider) + public async Task NotifyServerEvent(ServerEvent ev) { - logger.LogInformation("NotifyServerEvent: {Type} - {Details}", serverEvent.Type, serverEvent.ToString()); - await OnNotifyServerEvent?.Invoke(this, serverEvent)!; + _logger.LogInformation("NotifyServerEvent: {ev}", ev); + await OnNotifyServerEvent?.Invoke(this, ev); } public async Task NotifyNetwork(string network) @@ -115,10 +114,4 @@ public async Task PayInvoice(string bolt11, long? amountMilliSatosh return new PayResponse(PayResult.Error, e.Message); } } - - public event AsyncEventHandler? OnNewBlock; - public event AsyncEventHandler? OnTransactionDetected; - public event AsyncEventHandler? OnNotifyNetwork; - public event AsyncEventHandler? OnServerNodeInfo; - public event AsyncEventHandler? OnNotifyServerEvent; } diff --git a/BTCPayApp.Core/Attempt2/LDKKVStore.cs b/BTCPayApp.Core/Attempt2/LDKKVStore.cs index 2c8d09f..e08089f 100644 --- a/BTCPayApp.Core/Attempt2/LDKKVStore.cs +++ b/BTCPayApp.Core/Attempt2/LDKKVStore.cs @@ -13,30 +13,49 @@ public LDKKVStore(IConfigProvider configProvider) _configProvider = configProvider; } + private string CombineKey(string primary_namespace, string secondary_namespace, string key) + { + var str = "ln:"; + if (!string.IsNullOrEmpty(primary_namespace)) + { + str += primary_namespace + ":"; + } + if (!string.IsNullOrEmpty(secondary_namespace)) + { + str += secondary_namespace + ":"; + } + + if (!string.IsNullOrEmpty(key)) + { + str += key; + } + + return str; + } public Result_CVec_u8ZIOErrorZ read(string primary_namespace, string secondary_namespace, string key) { - var key1 = $"{primary_namespace}:{secondary_namespace}:{key}"; + var key1 = CombineKey(primary_namespace, secondary_namespace, key); var result = _configProvider.Get(key1).ConfigureAwait(false).GetAwaiter().GetResult(); return result == null ? Result_CVec_u8ZIOErrorZ.err(IOError.LDKIOError_NotFound) : Result_CVec_u8ZIOErrorZ.ok(result); } public Result_NoneIOErrorZ write(string primary_namespace, string secondary_namespace, string key, byte[] buf) { - var key1 = $"{primary_namespace}:{secondary_namespace}:{key}"; + var key1 = CombineKey(primary_namespace, secondary_namespace, key); _configProvider.Set(key1, buf).ConfigureAwait(false).GetAwaiter().GetResult(); return Result_NoneIOErrorZ.ok(); } public Result_NoneIOErrorZ remove(string primary_namespace, string secondary_namespace, string key, bool lazy) { - var key1 = $"{primary_namespace}:{secondary_namespace}:{key}"; + var key1 = CombineKey(primary_namespace, secondary_namespace, key); _configProvider.Set(key1, null).ConfigureAwait(false).GetAwaiter().GetResult(); return Result_NoneIOErrorZ.ok(); } public Result_CVec_StrZIOErrorZ list(string primary_namespace, string secondary_namespace) { - var key1 = $"{primary_namespace}:{secondary_namespace}:"; + var key1 = CombineKey(primary_namespace, secondary_namespace, string.Empty); var result = _configProvider.List(key1).ConfigureAwait(false).GetAwaiter().GetResult(); return Result_CVec_StrZIOErrorZ.ok(result.ToArray()); } diff --git a/BTCPayApp.Core/Attempt2/LDKNode.cs b/BTCPayApp.Core/Attempt2/LDKNode.cs index ce14083..fd58b4f 100644 --- a/BTCPayApp.Core/Attempt2/LDKNode.cs +++ b/BTCPayApp.Core/Attempt2/LDKNode.cs @@ -291,23 +291,23 @@ private async Task GetInitialChannelMonitors(EntropySource ent public async Task GetRawChannelManager() { - return await _configProvider.Get("ChannelManager") ?? null; + return await _configProvider.Get("ln:ChannelManager") ?? null; } public async Task UpdateChannelManager(ChannelManager serializedChannelManager) { - await _configProvider.Set("ChannelManager", serializedChannelManager.write()); + await _configProvider.Set("ln:ChannelManager", serializedChannelManager.write()); } public async Task UpdateNetworkGraph(NetworkGraph networkGraph) { - await _configProvider.Set("NetworkGraph", networkGraph.write()); + await _configProvider.Set("ln:NetworkGraph", networkGraph.write()); } public async Task UpdateScore(WriteableScore score) { - await _configProvider.Set("Score", score.write()); + await _configProvider.Set("ln:Score", score.write()); } @@ -351,33 +351,32 @@ await _connectionManager.HubProxy.TrackScripts(identifier, } } - public async Task UpdateChannel(string id, byte[] write) + public async Task UpdateChannel(List identifiers, byte[] write) { + var ids = identifiers.Select(alias => alias.Id).ToArray(); await using var context = await _dbContextFactory.CreateDbContextAsync(); - - var channel = await context.LightningChannels.SingleOrDefaultAsync(lightningChannel => lightningChannel.Id == id || lightningChannel.Aliases.Contains(id)); + var channel = (await context.ChannelAliases.Include(alias => alias.Channel) + .ThenInclude(channel1 => channel1.Aliases).FirstOrDefaultAsync(alias => ids.Contains(alias.Id)))?.Channel; if (channel is not null) { - if (!channel.Aliases.Contains(channel.Id)) - { - channel.Aliases.Add(channel.Id); - } - if (!channel.Aliases.Contains(id)) + foreach (var alias in identifiers) { - channel.Aliases.Add(id); + if (channel.Aliases.All(a => a.Id != alias.Id)) + { + channel.Aliases.Add(alias); + } } - channel.Id = id; channel.Data = write; } else { await context.LightningChannels.AddAsync(new Channel() { - Id = id, + Id = identifiers.First().ChannelId, Data = write, - Aliases = [id] + Aliases = identifiers.ToList() }); } await context.SaveChangesAsync(); diff --git a/BTCPayApp.Core/Attempt2/LightningNodeService.cs b/BTCPayApp.Core/Attempt2/LightningNodeService.cs index a47c84a..6339025 100644 --- a/BTCPayApp.Core/Attempt2/LightningNodeService.cs +++ b/BTCPayApp.Core/Attempt2/LightningNodeService.cs @@ -120,8 +120,8 @@ public async Task CleanseTask() await _onChainWalletManager.RemoveDerivation(WalletDerivation.LightningScripts); await using var context = await _dbContextFactory.CreateDbContextAsync(); context.LightningPayments.RemoveRange(context.LightningPayments); - context.Settings.RemoveRange(context.Settings.Where(s => new string[]{"ChannelManager","NetworkGraph","Score","lightningconfig"}.Contains(s.Key))); - + // context.OutboxItems.RemoveRange(context.OutboxItems); + context.Settings.RemoveRange(context.Settings.Where(s => s.Key.StartsWith("ln:"))); await context.SaveChangesAsync(); } finally diff --git a/BTCPayApp.Core/BTCPayApp.Core.csproj b/BTCPayApp.Core/BTCPayApp.Core.csproj index bfd88b3..509392f 100644 --- a/BTCPayApp.Core/BTCPayApp.Core.csproj +++ b/BTCPayApp.Core/BTCPayApp.Core.csproj @@ -12,6 +12,8 @@ + + diff --git a/BTCPayApp.Core/Data/AppDbContext.cs b/BTCPayApp.Core/Data/AppDbContext.cs index 0b3a94c..6b389fe 100644 --- a/BTCPayApp.Core/Data/AppDbContext.cs +++ b/BTCPayApp.Core/Data/AppDbContext.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; using BTCPayApp.CommonServer.Models; using BTCPayApp.Core.JsonConverters; using BTCPayApp.Core.LDK; @@ -19,12 +20,17 @@ public AppDbContext(DbContextOptions options) : base(options) public DbSet Settings { get; set; } public DbSet LightningChannels { get; set; } + public DbSet ChannelAliases { get; set; } public DbSet LightningPayments { get; set; } public DbSet OutboxItems { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { + modelBuilder.Entity() + .HasKey(w => new {w.Entity, w.Key, w.ActionType, w.Version}); + modelBuilder.Entity().Property(payment => payment.Timestamp).HasDefaultValueSql("datetime('now')"); + modelBuilder.Entity().Property(payment => payment.PaymentRequest) .HasConversion( request => request.ToString(), @@ -52,102 +58,159 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) //handling versioned data + //settings, channels, payments + + //when creating, set the version to 0 + //when updating, increment the version - modelBuilder.Entity() - .AfterDelete(trigger => - trigger.Action(group => - group.Insert( - @ref => new Outbox() - { - Version = @ref.Old.Version, - Key = "Channel-" + @ref.Old.Id, - ActionType = "delete" - }))); + // outbox creation + // when creating, insert an outbox item + // when updating, insert an outbox item + // when deleting, insert an outbox item modelBuilder.Entity() - .AfterDelete(trigger => trigger.Action(group => - group + .AfterInsert(trigger => trigger + .Action(group => group + .Condition(@ref => @ref.New.Backup) + .Insert( + // .InsertIfNotExists( (@ref, outbox) => outbox.Version == @ref.New.Version && outbox.ActionType == OutboxAction.Insert && outbox.Entity == "Setting" && outbox.Key == @ref.New.Key, + @ref => new Outbox() + { + Entity = "Setting", + Version = @ref.New.Version, + Key = @ref.New.Key, + ActionType = OutboxAction.Insert + }))) + .AfterDelete(trigger => trigger + .Action(group => group .Condition(@ref => @ref.Old.Backup) - .Insert( + .Insert( + // .InsertIfNotExists( (@ref, outbox) => @ref.Old.Version == outbox.Version && outbox.ActionType == OutboxAction.Delete && outbox.Entity == "Setting" && outbox.Key == @ref.Old.Key, @ref => new Outbox() { + Entity = "Setting", Version = @ref.Old.Version, - Key = "Setting-" + @ref.Old.Key, - ActionType = "delete" - }))); - - - foreach (var entityType in modelBuilder.Model.GetEntityTypes()) - { - if(typeof(VersionedData).IsAssignableFrom(entityType.ClrType)) - { - var builder = modelBuilder.Entity(entityType.ClrType); - - builder.Property("Version").IsConcurrencyToken().HasDefaultValue(0); - } - } - - modelBuilder.Entity() - .BeforeUpdate(trigger => trigger - .Action(action => action - .Condition(refs => refs.Old.Id == refs.New.Id) - .Update( - (tableRefs, entity) => tableRefs.Old.Id == entity.Id, - (tableRefs, oldChannel) => new Channel() {Version = oldChannel.Version + 1}) - .Insert(insert => new Outbox() - { - Key = "Channel-" + insert.New.Id, - Version = insert.New.Version, - ActionType = "update", - Timestamp = DateTimeOffset.UtcNow - })) - .Action(action => action - .Condition(refs => refs.Old.Id != refs.New.Id) - .Insert(insert => new Outbox() - { - Key = "Channel-" + insert.Old.Id, - Version = insert.Old.Version, - ActionType = "delete", - Timestamp = DateTimeOffset.UtcNow - }) - .Insert(insert => new Outbox() - { - Key = "Channel-" + insert.New.Id, - Version = insert.New.Version, - ActionType = "update", - Timestamp = DateTimeOffset.UtcNow - }))) - .AfterInsert(trigger => trigger - .Action(action => action - .Insert(insert => new Outbox() - { - Key = "Channel-" + insert.New.Id, - Version = insert.New.Version, - ActionType = "insert", - Timestamp = DateTimeOffset.UtcNow - }))).AfterDelete(trigger => trigger - .Action(action => action - .Insert(insert => new Outbox() - { - Key = "Channel-" + insert.Old.Id, - Version = insert.Old.Version, - ActionType = "delete", - Timestamp = DateTimeOffset.UtcNow - }))); - + Key = @ref.Old.Key, + ActionType = OutboxAction.Delete + }))) + .AfterUpdate(trigger => trigger + .Action(group => group.Update( + (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.Backup) + // .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.New.Version, + // Key = @ref.New.Key, + // ActionType = OutboxAction.Update + // }))); + + modelBuilder.Entity() + .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( + (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() + .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( + (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 + // }))); base.OnModelCreating(modelBuilder); } } - +public enum OutboxAction +{ + Insert, + Update, + Delete +} public class Outbox { - public DateTimeOffset Timestamp { get; set; } - public string ActionType { get; set; } + public DateTimeOffset Timestamp { get; set; } = DateTimeOffset.Now; + public OutboxAction ActionType { get; set; } public string Key { get; set; } + public string Entity { get; set; } public ulong Version { get; set; } } diff --git a/BTCPayApp.Core/Data/AppLightningPayment.cs b/BTCPayApp.Core/Data/AppLightningPayment.cs index c83563f..75dcbe1 100644 --- a/BTCPayApp.Core/Data/AppLightningPayment.cs +++ b/BTCPayApp.Core/Data/AppLightningPayment.cs @@ -32,6 +32,4 @@ public class AppLightningPayment : VersionedData public BOLT11PaymentRequest PaymentRequest { get; set; } [JsonExtensionData] public Dictionary AdditionalData { get; set; } = new(); - - public override string Entity => "LightningPayment"; } \ No newline at end of file diff --git a/BTCPayApp.Core/Data/Channel.cs b/BTCPayApp.Core/Data/Channel.cs index a376d1b..c2f3b30 100644 --- a/BTCPayApp.Core/Data/Channel.cs +++ b/BTCPayApp.Core/Data/Channel.cs @@ -3,9 +3,14 @@ public class Channel:VersionedData { public string Id { get; set; } - public List Aliases { get; set; } public byte[] Data { get; set; } + public List Aliases { get; set; } +} - - public override string Entity => "Channel"; +public class ChannelAlias +{ + public string Id { get; set; } + public string Type { get; set; } + public string ChannelId { get; set; } + public Channel Channel { get; set; } } diff --git a/BTCPayApp.Core/Data/DesignTimeAppContextFactory.cs b/BTCPayApp.Core/Data/DesignTimeAppContextFactory.cs index 835c666..543d392 100644 --- a/BTCPayApp.Core/Data/DesignTimeAppContextFactory.cs +++ b/BTCPayApp.Core/Data/DesignTimeAppContextFactory.cs @@ -1,4 +1,5 @@ -using Microsoft.EntityFrameworkCore; +using Laraue.EfCoreTriggers.SqlLite.Extensions; +using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Design; namespace BTCPayApp.Core.Data; @@ -9,6 +10,7 @@ public AppDbContext CreateDbContext(string[] args) { var optionsBuilder = new DbContextOptionsBuilder(); optionsBuilder.UseSqlite("Data Source=fake.db"); + optionsBuilder.UseSqlLiteTriggers(); return new AppDbContext(optionsBuilder.Options); } diff --git a/BTCPayApp.Core/Data/LightningConfig.cs b/BTCPayApp.Core/Data/LightningConfig.cs index faa7093..0d77fe8 100644 --- a/BTCPayApp.Core/Data/LightningConfig.cs +++ b/BTCPayApp.Core/Data/LightningConfig.cs @@ -3,7 +3,7 @@ namespace BTCPayApp.Core.Data; public class LightningConfig { - public const string Key = "lightningconfig"; + public const string Key = "ln:lightningconfig"; public string Alias { get; set; } = "BTCPay Server"; public string ScriptDerivationKey { get; set; } = WalletDerivation.NativeSegwit; //when ldk asks for an address, where do we get it from? diff --git a/BTCPayApp.Core/Data/Setting.cs b/BTCPayApp.Core/Data/Setting.cs index fb86aba..68e24b0 100644 --- a/BTCPayApp.Core/Data/Setting.cs +++ b/BTCPayApp.Core/Data/Setting.cs @@ -8,6 +8,4 @@ public class Setting:VersionedData public string Key { get; set; } public byte[] Value { get; set; } public bool Backup { get; set; } = true; - - public override string Entity => "Setting"; } diff --git a/BTCPayApp.Core/Data/VersionedData.cs b/BTCPayApp.Core/Data/VersionedData.cs index 3c6c264..a1b962b 100644 --- a/BTCPayApp.Core/Data/VersionedData.cs +++ b/BTCPayApp.Core/Data/VersionedData.cs @@ -5,6 +5,4 @@ namespace BTCPayApp.Core.Data; public abstract class VersionedData { public ulong Version { get; set; } = 0; - [NotMapped] - public abstract string Entity { get; } } \ No newline at end of file diff --git a/BTCPayApp.Core/DatabaseConfigProvider.cs b/BTCPayApp.Core/DatabaseConfigProvider.cs index fd88a80..6af5d01 100644 --- a/BTCPayApp.Core/DatabaseConfigProvider.cs +++ b/BTCPayApp.Core/DatabaseConfigProvider.cs @@ -83,17 +83,22 @@ using System.Text.Json; +using AsyncKeyedLock; using BTCPayApp.Core.Contracts; using BTCPayApp.Core.Data; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; public class DatabaseConfigProvider: IConfigProvider { private readonly IDbContextFactory _dbContextFactory; + private readonly ILogger _logger; + private AsyncKeyedLocker _lock = new(); - public DatabaseConfigProvider(IDbContextFactory dbContextFactory) + public DatabaseConfigProvider(IDbContextFactory dbContextFactory, ILogger logger) { _dbContextFactory = dbContextFactory; + _logger = logger; } public async Task Get(string key) @@ -107,6 +112,8 @@ public DatabaseConfigProvider(IDbContextFactory dbContextFactory) public async Task Set(string key, T? value) { + using var releaser = await _lock.LockAsync(key); + _logger.LogDebug("Setting {key} to {value}", key, value); await using var dbContext = await _dbContextFactory.CreateDbContextAsync(); if (value is null) { diff --git a/BTCPayApp.Core/LDK/LDKPersistInterface.cs b/BTCPayApp.Core/LDK/LDKPersistInterface.cs index ae10ac5..5479093 100644 --- a/BTCPayApp.Core/LDK/LDKPersistInterface.cs +++ b/BTCPayApp.Core/LDK/LDKPersistInterface.cs @@ -1,5 +1,6 @@ using System.Collections.Concurrent; using BTCPayApp.Core.Attempt2; +using BTCPayApp.Core.Data; using BTCPayApp.Core.Helpers; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -28,6 +29,8 @@ public LDKPersistInterface(LDKNode node, ILogger logger, IS private ConcurrentDictionary updateTasks = new(); + + public ChannelMonitorUpdateStatus persist_new_channel(OutPoint channel_funding_outpoint, ChannelMonitor data, MonitorUpdateId update_id) { @@ -44,9 +47,32 @@ public ChannelMonitorUpdateStatus persist_new_channel(OutPoint channel_funding_o var outs = data.get_outputs_to_watch() .SelectMany(zzzz => zzzz.get_b().Select(zz => Script.FromBytesUnsafe(zz.get_b()))).ToArray(); - var id = Convert.ToHexString(ChannelId.v1_from_funding_outpoint(channel_funding_outpoint).get_a()).ToLower(); + + var fundingId = Convert.ToHexString(ChannelId.v1_from_funding_outpoint(channel_funding_outpoint).get_a()).ToLower(); + + var identifiers = new List(); + identifiers.Add(new ChannelAlias() + { + Id = fundingId, + Type = "funding_outpoint" + }); + var otherId = data.channel_id().is_zero()? null: Convert.ToHexString(data.channel_id().get_a()).ToLower(); + if(otherId == fundingId) + { + otherId = null; + + } + if(otherId != null) + { + identifiers.Add(new ChannelAlias() + { + Id = otherId, + Type = "arbitrary_id" + }); + } + // var trackTask = _node.TrackScripts(outs).ContinueWith(task => _logger.LogDebug($"Tracking scripts finished for updateid: {update_id.hash()}"));; - var updateTask = _node.UpdateChannel(id, data.write()).ContinueWith(task => _logger.LogDebug($"Updating channel finished for updateid: {update_id.hash()}"));; + var updateTask = _node.UpdateChannel(identifiers, data.write()).ContinueWith(task => _logger.LogDebug($"Updating channel finished for updateid: {update_id.hash()}"));; await updateTask; await Task.Run(() => @@ -108,9 +134,31 @@ public ChannelMonitorUpdateStatus update_persisted_channel(OutPoint channel_fund var taskResult = updateTasks.GetOrAdd(updateId, async l => { - await _node.UpdateChannel( - Convert.ToHexString(ChannelId.v1_from_funding_outpoint(channel_funding_outpoint).get_a()).ToLower(), - data.write()); + + var fundingId = Convert.ToHexString(ChannelId.v1_from_funding_outpoint(channel_funding_outpoint).get_a()).ToLower(); + + var identifiers = new List(); + identifiers.Add(new ChannelAlias() + { + Id = fundingId, + Type = "funding_outpoint" + }); + var otherId = data.channel_id().is_zero()? null: Convert.ToHexString(data.channel_id().get_a()).ToLower(); + if(otherId == fundingId) + { + otherId = null; + + } + if(otherId != null) + { + identifiers.Add(new ChannelAlias() + { + Id = otherId, + Type = "arbitrary_id" + }); + } + + await _node.UpdateChannel(identifiers, data.write()); await Task.Run(() => { diff --git a/BTCPayApp.Core/Migrations/20240723141157_triggers.Designer.cs b/BTCPayApp.Core/Migrations/20240723141157_triggers.Designer.cs new file mode 100644 index 0000000..11de6a7 --- /dev/null +++ b/BTCPayApp.Core/Migrations/20240723141157_triggers.Designer.cs @@ -0,0 +1,201 @@ +// +using System; +using BTCPayApp.Core.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace BTCPayApp.Core.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20240723141157_triggers")] + partial class triggers + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.6"); + + modelBuilder.Entity("BTCPayApp.Core.Data.AppLightningPayment", b => + { + b.Property("PaymentHash") + .HasColumnType("TEXT"); + + b.Property("Inbound") + .HasColumnType("INTEGER"); + + b.Property("PaymentId") + .HasColumnType("TEXT"); + + b.Property("AdditionalData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("PaymentRequest") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Preimage") + .HasColumnType("TEXT"); + + b.Property("Secret") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Timestamp") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("INTEGER"); + + b.Property("Version") + .HasColumnType("INTEGER"); + + b.HasKey("PaymentHash", "Inbound", "PaymentId"); + + b.ToTable("LightningPayments", t => + { + t.HasTrigger("LC_TRIGGER_AFTER_DELETE_APPLIGHTNINGPAYMENT"); + + t.HasTrigger("LC_TRIGGER_AFTER_INSERT_APPLIGHTNINGPAYMENT"); + + t.HasTrigger("LC_TRIGGER_AFTER_UPDATE_APPLIGHTNINGPAYMENT"); + }); + + 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;"); + }); + + modelBuilder.Entity("BTCPayApp.Core.Data.Channel", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Data") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Version") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("LightningChannels", t => + { + t.HasTrigger("LC_TRIGGER_AFTER_DELETE_CHANNEL"); + + t.HasTrigger("LC_TRIGGER_AFTER_INSERT_CHANNEL"); + + t.HasTrigger("LC_TRIGGER_AFTER_UPDATE_CHANNEL"); + }); + + 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;"); + }); + + modelBuilder.Entity("BTCPayApp.Core.Data.ChannelAlias", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ChannelId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.ToTable("ChannelAliases"); + }); + + modelBuilder.Entity("BTCPayApp.Core.Data.Outbox", b => + { + b.Property("Entity") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("ActionType") + .HasColumnType("INTEGER"); + + b.Property("Version") + .HasColumnType("INTEGER"); + + b.Property("Timestamp") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValueSql("date()"); + + b.HasKey("Entity", "Key", "ActionType", "Version"); + + b.ToTable("OutboxItems"); + }); + + modelBuilder.Entity("BTCPayApp.Core.Data.Setting", b => + { + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Backup") + .HasColumnType("INTEGER"); + + b.Property("Value") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Version") + .HasColumnType("INTEGER"); + + b.HasKey("Key"); + + b.ToTable("Settings", t => + { + t.HasTrigger("LC_TRIGGER_AFTER_DELETE_SETTING"); + + t.HasTrigger("LC_TRIGGER_AFTER_INSERT_SETTING"); + + t.HasTrigger("LC_TRIGGER_AFTER_UPDATE_SETTING"); + }); + + 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;"); + }); + + modelBuilder.Entity("BTCPayApp.Core.Data.ChannelAlias", b => + { + b.HasOne("BTCPayApp.Core.Data.Channel", "Channel") + .WithMany("Aliases") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Channel"); + }); + + modelBuilder.Entity("BTCPayApp.Core.Data.Channel", b => + { + b.Navigation("Aliases"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/BTCPayApp.Core/Migrations/20240723141157_triggers.cs b/BTCPayApp.Core/Migrations/20240723141157_triggers.cs new file mode 100644 index 0000000..2b99a36 --- /dev/null +++ b/BTCPayApp.Core/Migrations/20240723141157_triggers.cs @@ -0,0 +1,155 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace BTCPayApp.Core.Migrations +{ + /// + public partial class triggers : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Aliases", + table: "LightningChannels"); + + migrationBuilder.AddColumn( + name: "Backup", + table: "Settings", + type: "INTEGER", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "Version", + table: "Settings", + type: "INTEGER", + nullable: false, + defaultValue: 0ul); + + migrationBuilder.AddColumn( + name: "Version", + table: "LightningPayments", + type: "INTEGER", + nullable: false, + defaultValue: 0ul); + + migrationBuilder.AddColumn( + name: "Version", + table: "LightningChannels", + type: "INTEGER", + nullable: false, + defaultValue: 0ul); + + migrationBuilder.CreateTable( + name: "ChannelAliases", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Type = table.Column(type: "TEXT", nullable: false), + ChannelId = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ChannelAliases", x => x.Id); + table.ForeignKey( + name: "FK_ChannelAliases_LightningChannels_ChannelId", + column: x => x.ChannelId, + principalTable: "LightningChannels", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OutboxItems", + columns: table => new + { + ActionType = table.Column(type: "INTEGER", nullable: false), + Key = table.Column(type: "TEXT", nullable: false), + Entity = table.Column(type: "TEXT", nullable: false), + Version = table.Column(type: "INTEGER", nullable: false), + Timestamp = table.Column(type: "TEXT", nullable: false, defaultValueSql: "date()") + }, + constraints: table => + { + table.PrimaryKey("PK_OutboxItems", x => new { x.Entity, x.Key, x.ActionType, x.Version }); + }); + + migrationBuilder.CreateIndex( + name: "IX_ChannelAliases_ChannelId", + table: "ChannelAliases", + column: "ChannelId"); + + migrationBuilder.Sql("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;"); + + 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_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_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;"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql("PRAGMA writable_schema = 1; \r\nDELETE FROM sqlite_master WHERE type = 'trigger' AND name like 'LC_TRIGGER_AFTER_DELETE_APPLIGHTNINGPAYMENT%';\r\nPRAGMA writable_schema = 0;"); + + migrationBuilder.Sql("PRAGMA writable_schema = 1; \r\nDELETE FROM sqlite_master WHERE type = 'trigger' AND name like 'LC_TRIGGER_AFTER_INSERT_APPLIGHTNINGPAYMENT%';\r\nPRAGMA writable_schema = 0;"); + + migrationBuilder.Sql("PRAGMA writable_schema = 1; \r\nDELETE FROM sqlite_master WHERE type = 'trigger' AND name like 'LC_TRIGGER_AFTER_UPDATE_APPLIGHTNINGPAYMENT%';\r\nPRAGMA writable_schema = 0;"); + + migrationBuilder.Sql("PRAGMA writable_schema = 1; \r\nDELETE FROM sqlite_master WHERE type = 'trigger' AND name like 'LC_TRIGGER_AFTER_DELETE_CHANNEL%';\r\nPRAGMA writable_schema = 0;"); + + migrationBuilder.Sql("PRAGMA writable_schema = 1; \r\nDELETE FROM sqlite_master WHERE type = 'trigger' AND name like 'LC_TRIGGER_AFTER_INSERT_CHANNEL%';\r\nPRAGMA writable_schema = 0;"); + + migrationBuilder.Sql("PRAGMA writable_schema = 1; \r\nDELETE FROM sqlite_master WHERE type = 'trigger' AND name like 'LC_TRIGGER_AFTER_UPDATE_CHANNEL%';\r\nPRAGMA writable_schema = 0;"); + + migrationBuilder.Sql("PRAGMA writable_schema = 1; \r\nDELETE FROM sqlite_master WHERE type = 'trigger' AND name like 'LC_TRIGGER_AFTER_DELETE_SETTING%';\r\nPRAGMA writable_schema = 0;"); + + migrationBuilder.Sql("PRAGMA writable_schema = 1; \r\nDELETE FROM sqlite_master WHERE type = 'trigger' AND name like 'LC_TRIGGER_AFTER_INSERT_SETTING%';\r\nPRAGMA writable_schema = 0;"); + + migrationBuilder.Sql("PRAGMA writable_schema = 1; \r\nDELETE FROM sqlite_master WHERE type = 'trigger' AND name like 'LC_TRIGGER_AFTER_UPDATE_SETTING%';\r\nPRAGMA writable_schema = 0;"); + + migrationBuilder.DropTable( + name: "ChannelAliases"); + + migrationBuilder.DropTable( + name: "OutboxItems"); + + migrationBuilder.DropColumn( + name: "Backup", + table: "Settings"); + + migrationBuilder.DropColumn( + name: "Version", + table: "Settings"); + + migrationBuilder.DropColumn( + name: "Version", + table: "LightningPayments"); + + migrationBuilder.DropColumn( + name: "Version", + table: "LightningChannels"); + + migrationBuilder.AddColumn( + name: "Aliases", + table: "LightningChannels", + type: "TEXT", + nullable: false, + defaultValue: ""); + } + } +} diff --git a/BTCPayApp.Core/Migrations/AppDbContextModelSnapshot.cs b/BTCPayApp.Core/Migrations/AppDbContextModelSnapshot.cs index 0b314fc..f2dde06 100644 --- a/BTCPayApp.Core/Migrations/AppDbContextModelSnapshot.cs +++ b/BTCPayApp.Core/Migrations/AppDbContextModelSnapshot.cs @@ -17,76 +17,180 @@ protected override void BuildModel(ModelBuilder modelBuilder) #pragma warning disable 612, 618 modelBuilder.HasAnnotation("ProductVersion", "8.0.6"); - modelBuilder.Entity("BTCPayApp.Core.Data.Channel", b => + modelBuilder.Entity("BTCPayApp.Core.Data.AppLightningPayment", b => { - b.Property("Id") + b.Property("PaymentHash") .HasColumnType("TEXT"); - b.Property("Aliases") + b.Property("Inbound") + .HasColumnType("INTEGER"); + + b.Property("PaymentId") + .HasColumnType("TEXT"); + + b.Property("AdditionalData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("PaymentRequest") .IsRequired() .HasColumnType("TEXT"); - b.Property("Data") + b.Property("Preimage") + .HasColumnType("TEXT"); + + b.Property("Secret") .IsRequired() - .HasColumnType("BLOB"); + .HasColumnType("TEXT"); - b.HasKey("Id"); + b.Property("Status") + .HasColumnType("INTEGER"); - b.ToTable("LightningChannels"); + b.Property("Timestamp") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("INTEGER"); + + b.Property("Version") + .HasColumnType("INTEGER"); + + b.HasKey("PaymentHash", "Inbound", "PaymentId"); + + b.ToTable("LightningPayments", t => + { + t.HasTrigger("LC_TRIGGER_AFTER_DELETE_APPLIGHTNINGPAYMENT"); + + t.HasTrigger("LC_TRIGGER_AFTER_INSERT_APPLIGHTNINGPAYMENT"); + + t.HasTrigger("LC_TRIGGER_AFTER_UPDATE_APPLIGHTNINGPAYMENT"); + }); + + 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;"); }); - modelBuilder.Entity("BTCPayApp.Core.Data.Setting", b => + modelBuilder.Entity("BTCPayApp.Core.Data.Channel", b => { - b.Property("Key") + b.Property("Id") .HasColumnType("TEXT"); - b.Property("Value") + b.Property("Data") .IsRequired() .HasColumnType("BLOB"); - b.HasKey("Key"); + b.Property("Version") + .HasColumnType("INTEGER"); - b.ToTable("Settings"); - }); + b.HasKey("Id"); - modelBuilder.Entity("BTCPayApp.Core.LDK.AppLightningPayment", b => - { - b.Property("PaymentHash") - .HasColumnType("TEXT"); + b.ToTable("LightningChannels", t => + { + t.HasTrigger("LC_TRIGGER_AFTER_DELETE_CHANNEL"); - b.Property("Inbound") - .HasColumnType("INTEGER"); + t.HasTrigger("LC_TRIGGER_AFTER_INSERT_CHANNEL"); - b.Property("PaymentId") + t.HasTrigger("LC_TRIGGER_AFTER_UPDATE_CHANNEL"); + }); + + 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;"); + }); + + modelBuilder.Entity("BTCPayApp.Core.Data.ChannelAlias", b => + { + b.Property("Id") .HasColumnType("TEXT"); - b.Property("AdditionalData") + b.Property("ChannelId") .IsRequired() - .HasColumnType("jsonb"); + .HasColumnType("TEXT"); - b.Property("PaymentRequest") + b.Property("Type") .IsRequired() .HasColumnType("TEXT"); - b.Property("Preimage") + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.ToTable("ChannelAliases"); + }); + + modelBuilder.Entity("BTCPayApp.Core.Data.Outbox", b => + { + b.Property("Entity") .HasColumnType("TEXT"); - b.Property("Secret") - .IsRequired() + b.Property("Key") .HasColumnType("TEXT"); - b.Property("Status") + b.Property("ActionType") + .HasColumnType("INTEGER"); + + b.Property("Version") .HasColumnType("INTEGER"); b.Property("Timestamp") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValueSql("date()"); + + b.HasKey("Entity", "Key", "ActionType", "Version"); + + b.ToTable("OutboxItems"); + }); + + modelBuilder.Entity("BTCPayApp.Core.Data.Setting", b => + { + b.Property("Key") .HasColumnType("TEXT"); - b.Property("Value") + b.Property("Backup") .HasColumnType("INTEGER"); - b.HasKey("PaymentHash", "Inbound", "PaymentId"); + b.Property("Value") + .IsRequired() + .HasColumnType("BLOB"); - b.ToTable("LightningPayments"); + b.Property("Version") + .HasColumnType("INTEGER"); + + b.HasKey("Key"); + + b.ToTable("Settings", t => + { + t.HasTrigger("LC_TRIGGER_AFTER_DELETE_SETTING"); + + t.HasTrigger("LC_TRIGGER_AFTER_INSERT_SETTING"); + + t.HasTrigger("LC_TRIGGER_AFTER_UPDATE_SETTING"); + }); + + 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;"); + }); + + modelBuilder.Entity("BTCPayApp.Core.Data.ChannelAlias", b => + { + b.HasOne("BTCPayApp.Core.Data.Channel", "Channel") + .WithMany("Aliases") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Channel"); + }); + + modelBuilder.Entity("BTCPayApp.Core.Data.Channel", b => + { + b.Navigation("Aliases"); }); #pragma warning restore 612, 618 } diff --git a/submodules/btcpayserver b/submodules/btcpayserver index 63cd54d..deb9c1e 160000 --- a/submodules/btcpayserver +++ b/submodules/btcpayserver @@ -1 +1 @@ -Subproject commit 63cd54d6fc4c595b96bf2c9b4795c9c157dd1a93 +Subproject commit deb9c1e1e1cdbb2f59225d231be09542479f0203 From 9c09a440e2eee4031be60cf0e0894ce855c28c2f Mon Sep 17 00:00:00 2001 From: Kukks Date: Thu, 25 Jul 2024 14:50:25 +0200 Subject: [PATCH 10/14] WIP --- BTCPayApp.Core/Attempt2/LDKKVStore.cs | 4 +- BTCPayApp.Core/Attempt2/LDKNode.cs | 8 +- .../Attempt2/OnChainWalletManager.cs | 6 +- BTCPayApp.Core/Auth/AuthStateProvider.cs | 6 +- BTCPayApp.Core/BTCPayApp.Core.csproj | 2 + BTCPayApp.Core/Contracts/IConfigProvider.cs | 2 +- .../Contracts/ISecureConfigProvider.cs | 7 +- BTCPayApp.Core/Data/AppDbContext.cs | 225 ++++++++---------- BTCPayApp.Core/Data/AppLightningPayment.cs | 2 +- BTCPayApp.Core/Data/Channel.cs | 7 +- BTCPayApp.Core/Data/EFExtensions.cs | 25 +- BTCPayApp.Core/Data/OutboxProcessor.cs | 209 ++++++++++++++++ BTCPayApp.Core/Data/Setting.cs | 1 + BTCPayApp.Core/Data/TLVHelper.cs | 43 ++++ BTCPayApp.Core/Data/VersionedData.cs | 5 +- BTCPayApp.Core/DatabaseConfigProvider.cs | 8 +- BTCPayApp.Core/LDK/PaymentsManager.cs | 4 +- ...cs => 20240724071302_triggers.Designer.cs} | 10 +- ...triggers.cs => 20240724071302_triggers.cs} | 8 +- .../Migrations/AppDbContextModelSnapshot.cs | 8 +- BTCPayApp.Core/StartupExtensions.cs | 7 +- BTCPayApp.Desktop/StartupExtensions.cs | 77 +++--- .../Pages/Settings/ChangePasscodePage.razor | 2 +- BTCPayApp.UI/Pages/Settings/IndexPage.razor | 2 +- BTCPayApp.UI/StateMiddleware.cs | 2 +- BTCPayApp.VSS/HttpVSSAPIClient.cs | 8 +- BTCPayApp.VSS/MultiValueDictionary.cs | 32 ++- BTCPayApp.VSS/VSSApiEncryptorClient.cs | 1 + 28 files changed, 485 insertions(+), 236 deletions(-) create mode 100644 BTCPayApp.Core/Data/OutboxProcessor.cs create mode 100644 BTCPayApp.Core/Data/TLVHelper.cs rename BTCPayApp.Core/Migrations/{20240723141157_triggers.Designer.cs => 20240724071302_triggers.Designer.cs} (92%) rename BTCPayApp.Core/Migrations/{20240723141157_triggers.cs => 20240724071302_triggers.cs} (92%) diff --git a/BTCPayApp.Core/Attempt2/LDKKVStore.cs b/BTCPayApp.Core/Attempt2/LDKKVStore.cs index e08089f..1bbe736 100644 --- a/BTCPayApp.Core/Attempt2/LDKKVStore.cs +++ b/BTCPayApp.Core/Attempt2/LDKKVStore.cs @@ -42,14 +42,14 @@ public Result_CVec_u8ZIOErrorZ read(string primary_namespace, string secondary_n 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(key1, null).ConfigureAwait(false).GetAwaiter().GetResult(); + _configProvider.Set(key1, null, true).ConfigureAwait(false).GetAwaiter().GetResult(); return Result_NoneIOErrorZ.ok(); } diff --git a/BTCPayApp.Core/Attempt2/LDKNode.cs b/BTCPayApp.Core/Attempt2/LDKNode.cs index fd58b4f..77e3d4f 100644 --- a/BTCPayApp.Core/Attempt2/LDKNode.cs +++ b/BTCPayApp.Core/Attempt2/LDKNode.cs @@ -202,7 +202,7 @@ public async Task GetJITLSPs() 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 @@ private async Task GetInitialChannelMonitors(EntropySource ent 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); } diff --git a/BTCPayApp.Core/Attempt2/OnChainWalletManager.cs b/BTCPayApp.Core/Attempt2/OnChainWalletManager.cs index 3f4bcfb..4b6666a 100644 --- a/BTCPayApp.Core/Attempt2/OnChainWalletManager.cs +++ b/BTCPayApp.Core/Attempt2/OnChainWalletManager.cs @@ -134,7 +134,7 @@ public async Task Generate() 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 async Task AddDerivation(string key, string name, string? descriptor) Descriptor = descriptor, Identifier = result[key] }; - await _configProvider.Set(WalletConfig.Key, WalletConfig); + await _configProvider.Set(WalletConfig.Key, WalletConfig, true); } finally { @@ -436,7 +436,7 @@ public async Task RemoveDerivation(params string[] key) 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 { diff --git a/BTCPayApp.Core/Auth/AuthStateProvider.cs b/BTCPayApp.Core/Auth/AuthStateProvider.cs index 2ddef8c..bbbf576 100644 --- a/BTCPayApp.Core/Auth/AuthStateProvider.cs +++ b/BTCPayApp.Core/Auth/AuthStateProvider.cs @@ -409,12 +409,12 @@ public async Task> GetAccounts(string? hostFilter = n 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(GetKey(account.Id), null); + await _config.Set(GetKey(account.Id), null, true); } private async Task GetAccount(string serverUrl, string email) @@ -435,7 +435,7 @@ private async Task SetCurrentAccount(BTCPayAccount? account) { 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; diff --git a/BTCPayApp.Core/BTCPayApp.Core.csproj b/BTCPayApp.Core/BTCPayApp.Core.csproj index 509392f..a3298c3 100644 --- a/BTCPayApp.Core/BTCPayApp.Core.csproj +++ b/BTCPayApp.Core/BTCPayApp.Core.csproj @@ -14,6 +14,8 @@ + + diff --git a/BTCPayApp.Core/Contracts/IConfigProvider.cs b/BTCPayApp.Core/Contracts/IConfigProvider.cs index 60deab2..d112198 100644 --- a/BTCPayApp.Core/Contracts/IConfigProvider.cs +++ b/BTCPayApp.Core/Contracts/IConfigProvider.cs @@ -3,6 +3,6 @@ public interface IConfigProvider { Task Get(string key); - Task Set(string key, T? value); + Task Set(string key, T? value, bool backup); Task> List(string prefix); } \ No newline at end of file diff --git a/BTCPayApp.Core/Contracts/ISecureConfigProvider.cs b/BTCPayApp.Core/Contracts/ISecureConfigProvider.cs index d8f09c5..d05384b 100644 --- a/BTCPayApp.Core/Contracts/ISecureConfigProvider.cs +++ b/BTCPayApp.Core/Contracts/ISecureConfigProvider.cs @@ -1,3 +1,8 @@ namespace BTCPayApp.Core.Contracts; -public interface ISecureConfigProvider : IConfigProvider; +public interface ISecureConfigProvider + +{ + Task Get(string key); + Task Set(string key, T? value); +} \ No newline at end of file diff --git a/BTCPayApp.Core/Data/AppDbContext.cs b/BTCPayApp.Core/Data/AppDbContext.cs index 6b389fe..8cf6c5a 100644 --- a/BTCPayApp.Core/Data/AppDbContext.cs +++ b/BTCPayApp.Core/Data/AppDbContext.cs @@ -30,7 +30,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity() .HasKey(w => new {w.Entity, w.Key, w.ActionType, w.Version}); modelBuilder.Entity().Property(payment => payment.Timestamp).HasDefaultValueSql("datetime('now')"); - + modelBuilder.Entity().Property(payment => payment.PaymentRequest) .HasConversion( request => request.ToString(), @@ -57,12 +57,12 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) //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 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) ActionType = OutboxAction.Delete }))) .AfterUpdate(trigger => trigger - .Action(group => group.Update( - (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( + (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() - .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( - (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() - .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( - (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() + .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( + (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 + }))); + + modelBuilder.Entity() + .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( + (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 _dbContextFactory; - - public OutboxProcessor(IDbContextFactory dbContextFactory) - { - _dbContextFactory = dbContextFactory; - } - - private async Task ProcessOutbox(CancellationToken cancellationToken = default) - { - await using var db = - new AppDbContext(new DbContextOptionsBuilder().UseSqlite("Data Source=outbox.db").Options); - var outbox = db.Set(); - 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; } } \ No newline at end of file diff --git a/BTCPayApp.Core/Data/AppLightningPayment.cs b/BTCPayApp.Core/Data/AppLightningPayment.cs index 75dcbe1..e428592 100644 --- a/BTCPayApp.Core/Data/AppLightningPayment.cs +++ b/BTCPayApp.Core/Data/AppLightningPayment.cs @@ -6,7 +6,7 @@ namespace BTCPayApp.Core.Data; -public class AppLightningPayment : VersionedData +public class AppLightningPayment : VersionedData { [JsonConverter(typeof(UInt256JsonConverter))] public uint256 PaymentHash { get; set; } diff --git a/BTCPayApp.Core/Data/Channel.cs b/BTCPayApp.Core/Data/Channel.cs index c2f3b30..a65e74e 100644 --- a/BTCPayApp.Core/Data/Channel.cs +++ b/BTCPayApp.Core/Data/Channel.cs @@ -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; } } + + diff --git a/BTCPayApp.Core/Data/EFExtensions.cs b/BTCPayApp.Core/Data/EFExtensions.cs index 7ffa930..79f8727 100644 --- a/BTCPayApp.Core/Data/EFExtensions.cs +++ b/BTCPayApp.Core/Data/EFExtensions.cs @@ -5,18 +5,19 @@ namespace BTCPayApp.Core.Data; public static class EFExtensions { - public static async Task CrappyUpsert(this DbContext ctx, T item, CancellationToken cancellationToken) + public static async Task Upsert(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); + // } } } \ No newline at end of file diff --git a/BTCPayApp.Core/Data/OutboxProcessor.cs b/BTCPayApp.Core/Data/OutboxProcessor.cs new file mode 100644 index 0000000..ff7e965 --- /dev/null +++ b/BTCPayApp.Core/Data/OutboxProcessor.cs @@ -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 _dbContextFactory; + private readonly BTCPayConnectionManager _btcPayConnectionManager; + private readonly ISecureConfigProvider _secureConfigProvider; + + public OutboxProcessor(IDbContextFactory dbContextFactory, BTCPayConnectionManager btcPayConnectionManager, ISecureConfigProvider secureConfigProvider) + { + _dbContextFactory = dbContextFactory; + _btcPayConnectionManager = btcPayConnectionManager; + _secureConfigProvider = secureConfigProvider; + } + + private async Task GetDataProtector() + { + var k = await _secureConfigProvider.Get("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 GetValue(AppDbContext dbContext, Outbox outbox) + { + var k = await _secureConfigProvider.Get("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); + } +} \ No newline at end of file diff --git a/BTCPayApp.Core/Data/Setting.cs b/BTCPayApp.Core/Data/Setting.cs index 68e24b0..2330648 100644 --- a/BTCPayApp.Core/Data/Setting.cs +++ b/BTCPayApp.Core/Data/Setting.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using System.Text.Json; namespace BTCPayApp.Core.Data; diff --git a/BTCPayApp.Core/Data/TLVHelper.cs b/BTCPayApp.Core/Data/TLVHelper.cs new file mode 100644 index 0000000..48eb287 --- /dev/null +++ b/BTCPayApp.Core/Data/TLVHelper.cs @@ -0,0 +1,43 @@ +namespace BTCPayApp.Core.Data; + +public static class TLVHelper +{ + public record TLV(byte Tag, byte[] Value); + + public static byte[] Write(List tlvList) + { + List byteArray = new List(); + + 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 Read(byte[] byteArray) + { + var tlvList = new List(); + 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; + } +} \ No newline at end of file diff --git a/BTCPayApp.Core/Data/VersionedData.cs b/BTCPayApp.Core/Data/VersionedData.cs index a1b962b..c40d279 100644 --- a/BTCPayApp.Core/Data/VersionedData.cs +++ b/BTCPayApp.Core/Data/VersionedData.cs @@ -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; -} \ No newline at end of file + public long Version { get; set; } = 0; +} diff --git a/BTCPayApp.Core/DatabaseConfigProvider.cs b/BTCPayApp.Core/DatabaseConfigProvider.cs index 6af5d01..9a9e579 100644 --- a/BTCPayApp.Core/DatabaseConfigProvider.cs +++ b/BTCPayApp.Core/DatabaseConfigProvider.cs @@ -110,10 +110,10 @@ public DatabaseConfigProvider(IDbContextFactory dbContextFactory, return config is null ? default : JsonSerializer.Deserialize(config.Value); } - public async Task Set(string key, T? value) + public async Task Set(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 async Task Set(string key, T? value) } 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); } diff --git a/BTCPayApp.Core/LDK/PaymentsManager.cs b/BTCPayApp.Core/LDK/PaymentsManager.cs index 8633d5b..80e50a1 100644 --- a/BTCPayApp.Core/LDK/PaymentsManager.cs +++ b/BTCPayApp.Core/LDK/PaymentsManager.cs @@ -297,8 +297,8 @@ public async Task CancelOutbound(string paymentId) 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); } diff --git a/BTCPayApp.Core/Migrations/20240723141157_triggers.Designer.cs b/BTCPayApp.Core/Migrations/20240724071302_triggers.Designer.cs similarity index 92% rename from BTCPayApp.Core/Migrations/20240723141157_triggers.Designer.cs rename to BTCPayApp.Core/Migrations/20240724071302_triggers.Designer.cs index 11de6a7..947724f 100644 --- a/BTCPayApp.Core/Migrations/20240723141157_triggers.Designer.cs +++ b/BTCPayApp.Core/Migrations/20240724071302_triggers.Designer.cs @@ -11,7 +11,7 @@ namespace BTCPayApp.Core.Migrations { [DbContext(typeof(AppDbContext))] - [Migration("20240723141157_triggers")] + [Migration("20240724071302_triggers")] partial class triggers { /// @@ -72,7 +72,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) 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 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) 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 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("Timestamp") .ValueGeneratedOnAdd() .HasColumnType("TEXT") - .HasDefaultValueSql("date()"); + .HasDefaultValueSql("datetime('now')"); b.HasKey("Entity", "Key", "ActionType", "Version"); @@ -177,7 +177,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) 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 => diff --git a/BTCPayApp.Core/Migrations/20240723141157_triggers.cs b/BTCPayApp.Core/Migrations/20240724071302_triggers.cs similarity index 92% rename from BTCPayApp.Core/Migrations/20240723141157_triggers.cs rename to BTCPayApp.Core/Migrations/20240724071302_triggers.cs index 2b99a36..89eac74 100644 --- a/BTCPayApp.Core/Migrations/20240723141157_triggers.cs +++ b/BTCPayApp.Core/Migrations/20240724071302_triggers.cs @@ -70,7 +70,7 @@ protected override void Up(MigrationBuilder migrationBuilder) Key = table.Column(type: "TEXT", nullable: false), Entity = table.Column(type: "TEXT", nullable: false), Version = table.Column(type: "INTEGER", nullable: false), - Timestamp = table.Column(type: "TEXT", nullable: false, defaultValueSql: "date()") + Timestamp = table.Column(type: "TEXT", nullable: false, defaultValueSql: "datetime('now')") }, constraints: table => { @@ -86,19 +86,19 @@ protected override void Up(MigrationBuilder migrationBuilder) 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;"); } /// diff --git a/BTCPayApp.Core/Migrations/AppDbContextModelSnapshot.cs b/BTCPayApp.Core/Migrations/AppDbContextModelSnapshot.cs index f2dde06..aad872f 100644 --- a/BTCPayApp.Core/Migrations/AppDbContextModelSnapshot.cs +++ b/BTCPayApp.Core/Migrations/AppDbContextModelSnapshot.cs @@ -69,7 +69,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) 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 @@ protected override void BuildModel(ModelBuilder modelBuilder) 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 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Timestamp") .ValueGeneratedOnAdd() .HasColumnType("TEXT") - .HasDefaultValueSql("date()"); + .HasDefaultValueSql("datetime('now')"); b.HasKey("Entity", "Key", "ActionType", "Version"); @@ -174,7 +174,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) 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 => diff --git a/BTCPayApp.Core/StartupExtensions.cs b/BTCPayApp.Core/StartupExtensions.cs index 7891d57..6140954 100644 --- a/BTCPayApp.Core/StartupExtensions.cs +++ b/BTCPayApp.Core/StartupExtensions.cs @@ -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.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 IServiceCollection ConfigureBTCPayAppCore(this IServiceCollection serviceCollection.AddSingleton(sp => (IAccountManager)sp.GetRequiredService()); serviceCollection.AddSingleton(); serviceCollection.AddLDK(); - return serviceCollection; } } - public class AppDatabaseMigrator: IHostedService { private readonly ILogger _logger; diff --git a/BTCPayApp.Desktop/StartupExtensions.cs b/BTCPayApp.Desktop/StartupExtensions.cs index f3b105c..46713e6 100644 --- a/BTCPayApp.Desktop/StartupExtensions.cs +++ b/BTCPayApp.Desktop/StartupExtensions.cs @@ -15,7 +15,6 @@ public static IServiceCollection ConfigureBTCPayAppDesktop(this IServiceCollecti serviceCollection.AddDataProtection(options => { options.ApplicationDiscriminator = "BTCPayApp"; - }); serviceCollection.AddSingleton(); // serviceCollection.AddSingleton(); @@ -25,56 +24,22 @@ public static IServiceCollection ConfigureBTCPayAppDesktop(this IServiceCollecti } } -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 ReadFromRaw(string str) => Task.FromResult(_dataProtector.Unprotect(str)); - protected override Task WriteFromRaw(string str) => Task.FromResult(_dataProtector.Protect(str)); -} - -public class FingerprintProvider: IFingerprint -{ - public Task GetAvailabilityAsync(bool allowAlternativeAuthentication = false) - { - return Task.FromResult(FingerprintAvailability.NoImplementation); - } - - public Task IsAvailableAsync(bool allowAlternativeAuthentication = false) - { - return Task.FromResult(false); - } - - public Task AuthenticateAsync(AuthenticationRequestConfiguration authRequestConfig, - CancellationToken cancellationToken = new CancellationToken()) - { - throw new NotImplementedException(); - } - - public Task GetAuthenticationTypeAsync() - { - throw new NotImplementedException(); - } -} - -public class DesktopConfigProvider : IConfigProvider -{ - private readonly Task _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 _configDir; public async Task Get(string key) { @@ -88,8 +53,6 @@ public DesktopConfigProvider(IDataDirectoryProvider directoryProvider) return JsonSerializer.Deserialize(json); } - protected virtual Task ReadFromRaw(string str) => Task.FromResult(str); - protected virtual Task WriteFromRaw(string str) => Task.FromResult(str); public async Task Set(string key, T? value) { @@ -117,8 +80,34 @@ public async Task> List(string prefix) } return Directory.GetFiles(dir, $"{prefix}*").Select(Path.GetFileName).Where(p => p?.StartsWith(prefix) is true)!; } + + protected Task ReadFromRaw(string str) => Task.FromResult(_dataProtector.Unprotect(str)); + protected Task WriteFromRaw(string str) => Task.FromResult(_dataProtector.Protect(str)); } +public class FingerprintProvider: IFingerprint +{ + public Task GetAvailabilityAsync(bool allowAlternativeAuthentication = false) + { + return Task.FromResult(FingerprintAvailability.NoImplementation); + } + + public Task IsAvailableAsync(bool allowAlternativeAuthentication = false) + { + return Task.FromResult(false); + } + + public Task AuthenticateAsync(AuthenticationRequestConfiguration authRequestConfig, + CancellationToken cancellationToken = new CancellationToken()) + { + throw new NotImplementedException(); + } + + public Task GetAuthenticationTypeAsync() + { + throw new NotImplementedException(); + } +} public class DesktopDataDirectoryProvider : IDataDirectoryProvider { private readonly IConfiguration _configuration; diff --git a/BTCPayApp.UI/Pages/Settings/ChangePasscodePage.razor b/BTCPayApp.UI/Pages/Settings/ChangePasscodePage.razor index 71b634d..4ce9392 100644 --- a/BTCPayApp.UI/Pages/Settings/ChangePasscodePage.razor +++ b/BTCPayApp.UI/Pages/Settings/ChangePasscodePage.razor @@ -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); } diff --git a/BTCPayApp.UI/Pages/Settings/IndexPage.razor b/BTCPayApp.UI/Pages/Settings/IndexPage.razor index f6f99cb..5a40810 100644 --- a/BTCPayApp.UI/Pages/Settings/IndexPage.razor +++ b/BTCPayApp.UI/Pages/Settings/IndexPage.razor @@ -209,7 +209,7 @@ if (HasPasscode) { _config!.Passcode = null; - await ConfigProvider.Set(BTCPayAppConfig.Key, _config); + await ConfigProvider.Set(BTCPayAppConfig.Key, _config, true); } } diff --git a/BTCPayApp.UI/StateMiddleware.cs b/BTCPayApp.UI/StateMiddleware.cs index 8b9f5cb..3fc1890 100644 --- a/BTCPayApp.UI/StateMiddleware.cs +++ b/BTCPayApp.UI/StateMiddleware.cs @@ -33,7 +33,7 @@ public override async Task InitializeAsync(IDispatcher dispatcher, IStore store) 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)); diff --git a/BTCPayApp.VSS/HttpVSSAPIClient.cs b/BTCPayApp.VSS/HttpVSSAPIClient.cs index 4404486..ed5b153 100644 --- a/BTCPayApp.VSS/HttpVSSAPIClient.cs +++ b/BTCPayApp.VSS/HttpVSSAPIClient.cs @@ -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; diff --git a/BTCPayApp.VSS/MultiValueDictionary.cs b/BTCPayApp.VSS/MultiValueDictionary.cs index fdbbac0..7bbd772 100644 --- a/BTCPayApp.VSS/MultiValueDictionary.cs +++ b/BTCPayApp.VSS/MultiValueDictionary.cs @@ -18,9 +18,9 @@ private class Bag : HashSet private readonly ConcurrentDictionary _dictionary; - public ConcurrentMultiDictionary() + public ConcurrentMultiDictionary(IEqualityComparer invariantCultureIgnoreCase = null) { - _dictionary = new ConcurrentDictionary(); + _dictionary = new ConcurrentDictionary(invariantCultureIgnoreCase); } public int Count => _dictionary.Count; @@ -41,7 +41,7 @@ public bool Add(TKey key, TValue value) public bool AddOrReplace(TKey key, TValue value) { - Remove(key, value); + Remove(key, value, out _); return Add(key, value); } @@ -60,8 +60,9 @@ public bool Remove(TKey key, out TValue[]? items) 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 bool Remove(TKey key, TValue value) } } 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 bool ContainsValue(IEnumerable value) { return _dictionary.Keys.Any(key => Contains(key, value)); } + + public bool ContainsValue(TKey key, TValue value) + { + return Contains(key, value); + } public IEnumerable GetKeysContainingValue(IEnumerable value) { return _dictionary.Keys.Where(key => Contains(key, value)); @@ -142,4 +148,20 @@ public IEnumerable GetKeysContainingValue(TValue value) { return _dictionary.Keys.Where(key => Contains(key, value)); } + + public bool RemoveValue(TValue value, out TKey[] keysRemoved) + { + List 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; + } } \ No newline at end of file diff --git a/BTCPayApp.VSS/VSSApiEncryptorClient.cs b/BTCPayApp.VSS/VSSApiEncryptorClient.cs index dd46618..50de59e 100644 --- a/BTCPayApp.VSS/VSSApiEncryptorClient.cs +++ b/BTCPayApp.VSS/VSSApiEncryptorClient.cs @@ -4,6 +4,7 @@ namespace BTCPayApp.VSS; + public class VSSApiEncryptorClient: IVSSAPI { private readonly IVSSAPI _vssApi; From 9b7a2ff7a22eb00891932663f936a32e0bb63e41 Mon Sep 17 00:00:00 2001 From: Kukks Date: Fri, 26 Jul 2024 11:28:12 +0200 Subject: [PATCH 11/14] moroe backup related code --- .../Attempt2/BTCPayConnectionManager.cs | 44 +++- .../Attempt2/OnChainWalletManager.cs | 3 - .../Attempt2/SingleKeyDataProtector.cs | 59 +++++ BTCPayApp.Core/Auth/AuthStateProvider.cs | 6 +- BTCPayApp.Core/Data/AppDbContext.cs | 42 ++-- BTCPayApp.Core/Data/AppLightningPayment.cs | 8 +- BTCPayApp.Core/Data/Channel.cs | 6 + BTCPayApp.Core/Data/OutboxProcessor.cs | 170 ++++++--------- .../Data/RemoteToLocalSyncService.cs | 206 ++++++++++++++++++ BTCPayApp.Core/Data/Setting.cs | 7 +- BTCPayApp.Core/Data/VersionedData.cs | 2 + BTCPayApp.Core/LDK/LDKExtensions.cs | 2 + ...cs => 20240726092628_triggers.Designer.cs} | 40 ++-- ...triggers.cs => 20240726092628_triggers.cs} | 67 ++++-- .../Migrations/AppDbContextModelSnapshot.cs | 38 ++-- .../Pages/Settings/ChangePasscodePage.razor | 2 +- BTCPayApp.UI/Pages/Settings/IndexPage.razor | 2 +- BTCPayApp.VSS/HttpVSSAPIClient.cs | 24 +- BTCPayApp.VSS/IVSSAPI.cs | 8 +- BTCPayApp.VSS/VSSApiEncryptorClient.cs | 16 +- submodules/btcpayserver | 2 +- 21 files changed, 549 insertions(+), 205 deletions(-) create mode 100644 BTCPayApp.Core/Attempt2/SingleKeyDataProtector.cs create mode 100644 BTCPayApp.Core/Data/RemoteToLocalSyncService.cs rename BTCPayApp.Core/Migrations/{20240724071302_triggers.Designer.cs => 20240726092628_triggers.Designer.cs} (88%) rename BTCPayApp.Core/Migrations/{20240724071302_triggers.cs => 20240726092628_triggers.cs} (83%) diff --git a/BTCPayApp.Core/Attempt2/BTCPayConnectionManager.cs b/BTCPayApp.Core/Attempt2/BTCPayConnectionManager.cs index 45c6b36..a7c09f9 100644 --- a/BTCPayApp.Core/Attempt2/BTCPayConnectionManager.cs +++ b/BTCPayApp.Core/Attempt2/BTCPayConnectionManager.cs @@ -1,8 +1,11 @@ using System.Net; using BTCPayApp.CommonServer; using BTCPayApp.Core.Auth; +using BTCPayApp.Core.Contracts; using BTCPayApp.Core.Helpers; +using BTCPayApp.VSS; using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.SignalR.Client; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -12,6 +15,23 @@ namespace BTCPayApp.Core.Attempt2; +public static class ConfigHelpers +{ + + public static async Task GetOrSet(this ISecureConfigProvider secureConfigProvider, string key, Func> factory) + { + var value = await secureConfigProvider.Get(key); + if (value is null) + { + value = await factory(); + await secureConfigProvider.Set(key, value); + } + + return value; + + } +} + public class BTCPayConnectionManager : IHostedService, IHubConnectionObserver { private readonly IAccountManager _accountManager; @@ -19,6 +39,8 @@ public class BTCPayConnectionManager : IHostedService, IHubConnectionObserver private readonly ILogger _logger; private readonly BTCPayAppServerClient _btcPayAppServerClient; private readonly IBTCPayAppHubClient _btcPayAppServerClientInterface; + private readonly IHttpClientFactory _httpClientFactory; + private readonly ISecureConfigProvider _secureConfigProvider; private IDisposable? _subscription; public IBTCPayAppHubServer? HubProxy { get; private set; } @@ -49,13 +71,17 @@ public BTCPayConnectionManager( AuthenticationStateProvider authStateProvider, ILogger logger, BTCPayAppServerClient btcPayAppServerClient, - IBTCPayAppHubClient btcPayAppServerClientInterface) + IBTCPayAppHubClient btcPayAppServerClientInterface, + IHttpClientFactory httpClientFactory, + ISecureConfigProvider secureConfigProvider) { _accountManager = accountManager; _authStateProvider = authStateProvider; _logger = logger; _btcPayAppServerClient = btcPayAppServerClient; _btcPayAppServerClientInterface = btcPayAppServerClientInterface; + _httpClientFactory = httpClientFactory; + _secureConfigProvider = secureConfigProvider; } public async Task StartAsync(CancellationToken cancellationToken) @@ -67,7 +93,23 @@ public async Task StartAsync(CancellationToken cancellationToken) await StartOrReplace(); _ = TryStayConnected(); } + + private async Task GetDataProtector() + { + var key = await _secureConfigProvider.GetOrSet("encryptionKey", async () => Convert.ToHexString(RandomUtils.GetBytes(32)).ToLowerInvariant()); + return new SingleKeyDataProtector(Convert.FromHexString(key)); + } + public async Task GetVSSAPI() + { + if (Connection is null) + throw new InvalidOperationException("Connection is not established"); + var vssUri = new Uri(new Uri(_accountManager.GetAccount().BaseUri), "vss"); + var vssClient = new HttpVSSAPIClient(vssUri, _httpClientFactory.CreateClient("vss")); + var protector = await GetDataProtector(); + return new VSSApiEncryptorClient(vssClient, protector); + } + private async Task OnServerNodeInfo(object? sender, string e) { ReportedNodeInfo = e; diff --git a/BTCPayApp.Core/Attempt2/OnChainWalletManager.cs b/BTCPayApp.Core/Attempt2/OnChainWalletManager.cs index efd9ef9..2eb7d7b 100644 --- a/BTCPayApp.Core/Attempt2/OnChainWalletManager.cs +++ b/BTCPayApp.Core/Attempt2/OnChainWalletManager.cs @@ -20,7 +20,6 @@ public class OnChainWalletManager : BaseHostedService private readonly BTCPayAppServerClient _btcPayAppServerClient; private readonly BTCPayConnectionManager _btcPayConnectionManager; private readonly ILogger _logger; - private readonly IDbContextFactory _dbContextFactory; private readonly IMemoryCache _memoryCache; private OnChainWalletState _state = OnChainWalletState.Init; @@ -48,14 +47,12 @@ public OnChainWalletManager( BTCPayAppServerClient btcPayAppServerClient, BTCPayConnectionManager btcPayConnectionManager, ILogger logger, - IDbContextFactory dbContextFactory, IMemoryCache memoryCache) { _configProvider = configProvider; _btcPayAppServerClient = btcPayAppServerClient; _btcPayConnectionManager = btcPayConnectionManager; _logger = logger; - _dbContextFactory = dbContextFactory; _memoryCache = memoryCache; } diff --git a/BTCPayApp.Core/Attempt2/SingleKeyDataProtector.cs b/BTCPayApp.Core/Attempt2/SingleKeyDataProtector.cs new file mode 100644 index 0000000..af32681 --- /dev/null +++ b/BTCPayApp.Core/Attempt2/SingleKeyDataProtector.cs @@ -0,0 +1,59 @@ +using System.Security.Cryptography; +using System.Text; +using Microsoft.AspNetCore.DataProtection; + +namespace BTCPayApp.Core.Attempt2; + +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); + } + +} \ No newline at end of file diff --git a/BTCPayApp.Core/Auth/AuthStateProvider.cs b/BTCPayApp.Core/Auth/AuthStateProvider.cs index e8313c8..f3fe32f 100644 --- a/BTCPayApp.Core/Auth/AuthStateProvider.cs +++ b/BTCPayApp.Core/Auth/AuthStateProvider.cs @@ -412,12 +412,12 @@ public async Task> GetAccounts(string? hostFilter = n public async Task UpdateAccount(BTCPayAccount account) { - await _config.Set(GetKey(account.Id), account, true); + await _config.Set(GetKey(account.Id), account, false); } public async Task RemoveAccount(BTCPayAccount account) { - await _config.Set(GetKey(account.Id), null, true); + await _config.Set(GetKey(account.Id), null, false); } private async Task GetAccount(string serverUrl, string email) @@ -438,7 +438,7 @@ private async Task SetCurrentAccount(BTCPayAccount? account) { OnBeforeAccountChange?.Invoke(this, _account); if (account != null) await UpdateAccount(account); - await _config.Set(CurrentAccountKey, account?.Id, true); + await _config.Set(CurrentAccountKey, account?.Id, false); _account = account; _userInfo = null; diff --git a/BTCPayApp.Core/Data/AppDbContext.cs b/BTCPayApp.Core/Data/AppDbContext.cs index 8cf6c5a..b286786 100644 --- a/BTCPayApp.Core/Data/AppDbContext.cs +++ b/BTCPayApp.Core/Data/AppDbContext.cs @@ -5,6 +5,7 @@ using BTCPayApp.Core.LDK; using BTCPayServer.Lightning; using Laraue.EfCoreTriggers.Common.Extensions; +using Laraue.EfCoreTriggers.Common.TriggerBuilders.Actions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Hosting; using NBitcoin; @@ -70,17 +71,20 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity() .AfterInsert(trigger => trigger - .Action(group => group - .Condition(@ref => @ref.New.Backup) - .Insert( - // .InsertIfNotExists( (@ref, outbox) => outbox.Version == @ref.New.Version && outbox.ActionType == OutboxAction.Insert && outbox.Entity == "Setting" && outbox.Key == @ref.New.Key, - @ref => new Outbox() - { - Entity = "Setting", - Version = @ref.New.Version, - Key = @ref.New.Key, - ActionType = OutboxAction.Insert - }))) + .Action(group => + { + group + .Condition(@ref => @ref.New.Backup) + .Insert( + // .InsertIfNotExists( (@ref, outbox) => outbox.Version == @ref.New.Version && outbox.ActionType == OutboxAction.Insert && outbox.Entity == "Setting" && outbox.Key == @ref.New.Key, + @ref => new Outbox() + { + Entity = "Setting", + Version = @ref.New.Version, + Key = @ref.New.EntityKey, + ActionType = OutboxAction.Insert + }); + })) .AfterDelete(trigger => trigger .Action(group => group .Condition(@ref => @ref.Old.Backup) @@ -90,7 +94,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) { Entity = "Setting", Version = @ref.Old.Version, - Key = @ref.Old.Key, + Key = @ref.Old.EntityKey, ActionType = OutboxAction.Delete }))) .AfterUpdate(trigger => trigger @@ -105,7 +109,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) { Entity = "Setting", Version = @ref.Old.Version + 1, - Key = @ref.New.Key, + Key = @ref.New.EntityKey, ActionType = OutboxAction.Update }))); // .Action(group => group @@ -129,7 +133,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) { Entity = "Channel", Version = @ref.New.Version, - Key = @ref.New.Id, + Key = @ref.New.EntityKey, ActionType = OutboxAction.Insert }))) .AfterDelete(trigger => trigger @@ -140,7 +144,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) { Entity = "Channel", Version = @ref.Old.Version, - Key = @ref.Old.Id, + Key = @ref.Old.EntityKey, ActionType = OutboxAction.Delete }))) .AfterUpdate(trigger => trigger @@ -152,7 +156,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) { Entity = "Channel", Version = @ref.Old.Version +1, - Key = @ref.New.Id, + Key = @ref.New.EntityKey, ActionType = OutboxAction.Update }))); @@ -165,7 +169,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) { Entity = "Payment", Version = @ref.New.Version, - Key = @ref.New.PaymentHash + "_" + @ref.New.PaymentId + "_" + @ref.New.Inbound, + Key = @ref.New.EntityKey, ActionType = OutboxAction.Insert }))) .AfterDelete(trigger => trigger @@ -176,7 +180,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) { Entity = "Payment", Version = @ref.Old.Version, - Key = @ref.Old.PaymentHash + "_" + @ref.Old.PaymentId + "_" + @ref.Old.Inbound, + Key = @ref.Old.EntityKey, ActionType = OutboxAction.Delete }))) .AfterUpdate(trigger => trigger @@ -191,7 +195,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) { Entity = "Payment", Version = @ref.Old.Version +1, - Key = @ref.New.PaymentHash + "_" + @ref.New.PaymentId + "_" + @ref.New.Inbound, + Key = @ref.New.EntityKey, ActionType = OutboxAction.Update }))); base.OnModelCreating(modelBuilder); diff --git a/BTCPayApp.Core/Data/AppLightningPayment.cs b/BTCPayApp.Core/Data/AppLightningPayment.cs index e428592..edf76c6 100644 --- a/BTCPayApp.Core/Data/AppLightningPayment.cs +++ b/BTCPayApp.Core/Data/AppLightningPayment.cs @@ -6,7 +6,7 @@ namespace BTCPayApp.Core.Data; -public class AppLightningPayment : VersionedData +public class AppLightningPayment : VersionedData { [JsonConverter(typeof(UInt256JsonConverter))] public uint256 PaymentHash { get; set; } @@ -32,4 +32,10 @@ public class AppLightningPayment : VersionedData public BOLT11PaymentRequest PaymentRequest { get; set; } [JsonExtensionData] public Dictionary AdditionalData { get; set; } = new(); + + public override string EntityKey + { + get => $"Payment_{PaymentHash}_{PaymentId}_{Inbound}"; + init { } + } } \ No newline at end of file diff --git a/BTCPayApp.Core/Data/Channel.cs b/BTCPayApp.Core/Data/Channel.cs index a65e74e..3f87d78 100644 --- a/BTCPayApp.Core/Data/Channel.cs +++ b/BTCPayApp.Core/Data/Channel.cs @@ -7,6 +7,12 @@ public class Channel:VersionedData public string Id { get; set; } public byte[] Data { get; set; } public List Aliases { get; set; } + + public override string EntityKey + { + get => $"Channel_{Id}"; + init { } + } } public class ChannelAlias diff --git a/BTCPayApp.Core/Data/OutboxProcessor.cs b/BTCPayApp.Core/Data/OutboxProcessor.cs index ff7e965..66ad6b8 100644 --- a/BTCPayApp.Core/Data/OutboxProcessor.cs +++ b/BTCPayApp.Core/Data/OutboxProcessor.cs @@ -13,102 +13,28 @@ 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 _dbContextFactory; private readonly BTCPayConnectionManager _btcPayConnectionManager; - private readonly ISecureConfigProvider _secureConfigProvider; - public OutboxProcessor(IDbContextFactory dbContextFactory, BTCPayConnectionManager btcPayConnectionManager, ISecureConfigProvider secureConfigProvider) + public OutboxProcessor(IDbContextFactory dbContextFactory, + BTCPayConnectionManager btcPayConnectionManager) { _dbContextFactory = dbContextFactory; _btcPayConnectionManager = btcPayConnectionManager; - _secureConfigProvider = secureConfigProvider; } - private async Task GetDataProtector() - { - var k = await _secureConfigProvider.Get("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 GetValue(AppDbContext dbContext, Outbox outbox) { - var k = await _secureConfigProvider.Get("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) + var setting = await dbContext.Settings.SingleOrDefaultAsync(setting1 => setting1.EntityKey == outbox.Key && setting1.Backup); + if (setting == null) return null; return new KeyValue() { @@ -117,9 +43,10 @@ private async Task GetDataProtector() Version = setting.Version }; case "Channel": - var channel = await dbContext.LightningChannels.Include(channel1 => channel1.Aliases).SingleOrDefaultAsync(channel1 => channel1.Id == outbox.Key); - - if(channel == null) + var channel = await dbContext.LightningChannels.Include(channel1 => channel1.Aliases) + .SingleOrDefaultAsync(channel1 => channel1.EntityKey == outbox.Key); + + if (channel == null) return null; var val = JsonSerializer.SerializeToUtf8Bytes(channel); return new KeyValue() @@ -129,13 +56,8 @@ private async Task GetDataProtector() 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) + var payment = await dbContext.LightningPayments .SingleOrDefaultAsync(lightningPayment => lightningPayment.EntityKey == outbox.Key); + if (payment == null) return null; var paymentBytes = JsonSerializer.SerializeToUtf8Bytes(payment); return new KeyValue() @@ -146,15 +68,60 @@ private async Task GetDataProtector() }; default: throw new ArgumentOutOfRangeException(); - } + + // 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 = "Setting_" + 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 = "Channel_" + 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 = "Payment_" + outbox.Key, + // Value = ByteString.CopyFrom(paymentBytes), + // Version = payment.Version + // }; + // default: + // throw new ArgumentOutOfRangeException(); + // } } private async Task ProcessOutbox(CancellationToken cancellationToken = default) { + var backupAPi = await _btcPayConnectionManager.GetVSSAPI(); 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); @@ -164,30 +131,33 @@ private async Task ProcessOutbox(CancellationToken cancellationToken = default) .ThenByDescending(outbox1 => outbox1.ActionType).ToArray(); foreach (var item in orderedEnumerable) { - if (item.ActionType == OutboxAction.Delete ) + if (item.ActionType == OutboxAction.Delete) { putObjectRequest.DeleteItems.Add(new KeyValue() { - Key = item.Entity, Version = item.Version + Key = item.Key, Version = item.Version }); } else { - var kv = await GetValue(db, item); - if (kv != null) - { - putObjectRequest.TransactionItems.Add(kv); - break; - } + var kv = await GetValue(db, item); + if (kv != null) + { + putObjectRequest.TransactionItems.Add(kv); + break; + } } } + db.OutboxItems.RemoveRange(orderedEnumerable); // Process outbox item } - + + await backupAPi.PutObjectAsync(putObjectRequest, cancellationToken); } private CancellationTokenSource _cts = new(); + public async Task StartAsync(CancellationToken cancellationToken) { _cts = new CancellationTokenSource(); diff --git a/BTCPayApp.Core/Data/RemoteToLocalSyncService.cs b/BTCPayApp.Core/Data/RemoteToLocalSyncService.cs new file mode 100644 index 0000000..fcd7de4 --- /dev/null +++ b/BTCPayApp.Core/Data/RemoteToLocalSyncService.cs @@ -0,0 +1,206 @@ +using BTCPayApp.Core.Attempt2; +using BTCPayApp.Core.Helpers; +using Microsoft.AspNetCore.DataProtection; +using Microsoft.EntityFrameworkCore; +using VSSProto; + +namespace BTCPayApp.Core.Data; + +public class RemoteToLocalSyncService : IScopedHostedService +{ + private readonly IDbContextFactory _dbContextFactory; + private readonly BTCPayConnectionManager _btcPayConnectionManager; + private readonly IDataProtector _dataProtector; + + public RemoteToLocalSyncService(IDbContextFactory dbContextFactory, + BTCPayConnectionManager btcPayConnectionManager, IDataProtectionProvider dataProtectionProvider) + { + _dbContextFactory = dbContextFactory; + _btcPayConnectionManager = btcPayConnectionManager; + _dataProtector = dataProtectionProvider.CreateProtector("RemoteToLocalSyncService"); + } + + private async Task CreateLocalVersions(AppDbContext dbContext) + { + var settings = dbContext.Settings.Where(setting => setting.Backup).Select(setting => new KeyValue() + { + Key = "Setting_" + setting.Key, + Version = setting.Version + }); + var channels = dbContext.LightningChannels.Select(channel => new KeyValue() + { + Key = "Channel_" + channel.Id, + Version = channel.Version + }); + var payments = dbContext.LightningPayments.Select(payment => new KeyValue() + { + Key = $"Payment_{payment.PaymentHash}_{payment.PaymentId}_{payment.Inbound}", + Version = payment.Version + }); + return await settings.Concat(channels).Concat(payments).ToArrayAsync(); + } + + public async Task Sync() + { + var backupApi = await _btcPayConnectionManager.GetVSSAPI(); + await using var db = await _dbContextFactory.CreateDbContextAsync(); + var localVersions = await CreateLocalVersions(db); + var remoteVersions = await backupApi.ListKeyVersionsAsync(new ListKeyVersionsRequest()); + await db.Database.BeginTransactionAsync(); + try + { + await db.Database.ExecuteSqlRawAsync("//sql to disable all triggers"); + // + // // see which ones to delete + // var toDelete = localVersions.Where(localVersion => + // remoteVersions.KeyVersions(remoteVersion => remoteVersion.Key != localVersion.Key)).ToArray(); + // + // var settingsToDelete = toDelete.Where(key => key.Key.StartsWith("Setting_")).Select(key => key.Key.Split('_')[1]); + // var channelsToDelete = toDelete.Where(key => key.Key.StartsWith("Channel_")).Select(key => key.Key.Split('_')[1]); + // var paymentsToDelete = toDelete.Where(key => key.Key.StartsWith("Payment_")).Select(key => + // { + // var keys = key.Key.Split('_').Skip(1).ToArray(); + // return (uint256.Parse(keys[0]), keys[1], bool.Parse(keys[2])); + // }); + // await db.Settings.Where(setting => settingsToDelete.Contains(setting.Key)).ExecuteDeleteAsync(); + // await db.LightningChannels.Where(channel => channelsToDelete.Contains(channel.Key)).ExecuteDeleteAsync(); + // await db.LightningPayments.Where(payment => paymentsToDelete.Contains((payment.PaymentHash, payment.PaymentId, payment.Inbound))).ExecuteDeleteAsync(); + // + // + // + // foreach (var key in toDelete) + // { + // if (key.Key.StartsWith("Setting_")) + // { + // var settingKey = key.Key.Split('_')[1]; + // var setting = await db.Settings.FindAsync(settingKey); + // if (setting != null) + // { + // db.Settings.Remove(setting); + // } + // } + // else if (key.Key.StartsWith( "Channel_")) + // { + // var channelId = key.Key.Split('_')[1]; + // var channel = await db.LightningChannels.FindAsync(channelId); + // if (channel != null) + // { + // db.LightningChannels.Remove(channel); + // } + // } + // else if (key.Key.StartsWith( "Payment_")) + // { + // var split = key.Key.Split('_'); + // var paymentHash = uint256.Parse(split[1]); + // var paymentId = split[2]; + // var inbound = bool.Parse(split[3]); + // var payment = await db.LightningPayments.FindAsync(paymentHash, paymentId, inbound); + // if (payment != null) + // { + // db.LightningPayments.Remove(payment); + // } + // } + // else + // { + // throw new ArgumentOutOfRangeException(); + // } + // } + // var toUpdate = localVersions.Where(localVersion => + // remoteVersions.KeyVersions(remoteVersion => remoteVersion.Key > localVersion.Key)).ToArray(); + // // upsert the rest when needed + // foreach (var remoteVersion in remoteVersions.KeyVersions) + // { + // var localVersion = localVersions.FirstOrDefault(localVersion => localVersion.Key == remoteVersion.Key); + // if (localVersion == null || localVersion.Version < remoteVersion.Version) + // { + // var kv = await backupApi.GetObjectAsync(new GetObjectRequest() + // { + // Key = remoteVersion.Key + // }); + // if (kv != null) + // { + // if (remoteVersion.Key.StartsWith("Setting_")) + // { + // var settingKey = remoteVersion.Key.Split('_')[1]; + // var setting = await db.Settings.FindAsync(settingKey); + // if (setting == null) + // { + // setting = new Setting() + // { + // Key = settingKey + // }; + // db.Settings.Add(setting); + // } + // + // setting.Value = kv.Value.ToByteArray(); + // setting.Version = kv.Version; + // } + // else if (remoteVersion.Key.StartsWith("Channel_")) + // { + // var channelId = remoteVersion.Key.Split('_')[1]; + // var channel = await db.LightningChannels.FindAsync(channelId); + // if (channel == null) + // { + // channel = new LightningChannel() + // { + // Id = channelId + // }; + // db.LightningChannels.Add(channel); + // } + // + // var channelData = JsonSerializer.Deserialize(kv.Value.ToStringUtf8()); + // channel.Aliases = channelData.Aliases; + // channel.Version = kv.Version; + // } + // else if (remoteVersion.Key.StartsWith("Payment_")) + // { + // var split = remoteVersion.Key.Split('_'); + // var paymentHash = uint256.Parse(split[1]); + // var paymentId = split[2]; + // var inbound = bool.Parse(split[3]); + // var payment = await db.LightningPayments.FindAsync(paymentHash, paymentId, inbound); + // if (payment == null) + // { + // payment = new LightningPayment() + // { + // PaymentHash = paymentHash, + // PaymentId = paymentId, + // Inbound = inbound + // }; + // db.LightningPayments.Add(payment); + // } + // + // var paymentData = JsonSerializer.Deserialize(kv.Value.ToStringUtf8()); + // payment.Amount = paymentData.Amount; + // payment.Bolt11 = paymentData.Bolt11; + // payment.Version = kv.Version; + // } + // else + // { + // throw new ArgumentOutOfRangeException(); + // } + // } + // } + // } + // + // + await db.Database.ExecuteSqlRawAsync("//sql to reenable all triggers"); + await db.Database.CommitTransactionAsync(); + } + catch (Exception e) + { + await db.Database.RollbackTransactionAsync(); + throw; + } + } + + public async Task StartAsync(CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public async Task StopAsync(CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/BTCPayApp.Core/Data/Setting.cs b/BTCPayApp.Core/Data/Setting.cs index 2330648..bce2fc4 100644 --- a/BTCPayApp.Core/Data/Setting.cs +++ b/BTCPayApp.Core/Data/Setting.cs @@ -1,5 +1,4 @@ using System.ComponentModel.DataAnnotations; -using System.Text.Json; namespace BTCPayApp.Core.Data; @@ -9,4 +8,10 @@ public class Setting:VersionedData public string Key { get; set; } public byte[] Value { get; set; } public bool Backup { get; set; } = true; + + public override string EntityKey + { + get => $"Setting_{Key}"; + init { } + } } diff --git a/BTCPayApp.Core/Data/VersionedData.cs b/BTCPayApp.Core/Data/VersionedData.cs index c40d279..7f1da49 100644 --- a/BTCPayApp.Core/Data/VersionedData.cs +++ b/BTCPayApp.Core/Data/VersionedData.cs @@ -6,4 +6,6 @@ namespace BTCPayApp.Core.Data; public abstract class VersionedData { public long Version { get; set; } = 0; + + public abstract string EntityKey { get; init; } } diff --git a/BTCPayApp.Core/LDK/LDKExtensions.cs b/BTCPayApp.Core/LDK/LDKExtensions.cs index f2275d5..c6ecbdf 100644 --- a/BTCPayApp.Core/LDK/LDKExtensions.cs +++ b/BTCPayApp.Core/LDK/LDKExtensions.cs @@ -2,6 +2,7 @@ using System.Net.Sockets; using BTCPayApp.Core.Attempt2; using BTCPayApp.Core.Contracts; +using BTCPayApp.Core.Data; using BTCPayApp.Core.Helpers; using BTCPayApp.Core.LSP.JIT; using Microsoft.Extensions.DependencyInjection; @@ -213,6 +214,7 @@ public static IServiceCollection AddLDK(this IServiceCollection services) services.AddScoped(); // services.AddScoped(provider => // provider.GetRequiredService()); + services.AddScoped(provider => provider.GetRequiredService()); services.AddScoped(provider => provider.GetRequiredService()); services.AddScoped(provider => provider.GetRequiredService()); services.AddScoped(provider => provider.GetRequiredService()); diff --git a/BTCPayApp.Core/Migrations/20240724071302_triggers.Designer.cs b/BTCPayApp.Core/Migrations/20240726092628_triggers.Designer.cs similarity index 88% rename from BTCPayApp.Core/Migrations/20240724071302_triggers.Designer.cs rename to BTCPayApp.Core/Migrations/20240726092628_triggers.Designer.cs index 947724f..20061b2 100644 --- a/BTCPayApp.Core/Migrations/20240724071302_triggers.Designer.cs +++ b/BTCPayApp.Core/Migrations/20240726092628_triggers.Designer.cs @@ -11,7 +11,7 @@ namespace BTCPayApp.Core.Migrations { [DbContext(typeof(AppDbContext))] - [Migration("20240724071302_triggers")] + [Migration("20240726092628_triggers")] partial class triggers { /// @@ -35,6 +35,10 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("jsonb"); + b.Property("EntityKey") + .IsRequired() + .HasColumnType("TEXT"); + b.Property("PaymentRequest") .IsRequired() .HasColumnType("TEXT"); @@ -55,7 +59,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("Value") .HasColumnType("INTEGER"); - b.Property("Version") + b.Property("Version") .HasColumnType("INTEGER"); b.HasKey("PaymentHash", "Inbound", "PaymentId"); @@ -70,9 +74,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) }); 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\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;"); + .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.\"EntityKey\", \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.\"EntityKey\", \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\n INSERT INTO \"OutboxItems\" (\"Entity\", \"Version\", \"Key\", \"ActionType\") SELECT 'Payment', \r\n OLD.\"Version\" + 1, \r\n NEW.\"EntityKey\", \r\n 1;\r\nEND;"); }); modelBuilder.Entity("BTCPayApp.Core.Data.Channel", b => @@ -84,7 +88,11 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("BLOB"); - b.Property("Version") + b.Property("EntityKey") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Version") .HasColumnType("INTEGER"); b.HasKey("Id"); @@ -99,9 +107,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) }); 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\n INSERT INTO \"OutboxItems\" (\"Entity\", \"Version\", \"Key\", \"ActionType\") SELECT 'Channel', \r\n OLD.\"Version\" + 1, \r\n NEW.\"Id\", \r\n 1;\r\nEND;"); + .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.\"EntityKey\", \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.\"EntityKey\", \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\n INSERT INTO \"OutboxItems\" (\"Entity\", \"Version\", \"Key\", \"ActionType\") SELECT 'Channel', \r\n OLD.\"Version\" + 1, \r\n NEW.\"EntityKey\", \r\n 1;\r\nEND;"); }); modelBuilder.Entity("BTCPayApp.Core.Data.ChannelAlias", b => @@ -135,7 +143,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("ActionType") .HasColumnType("INTEGER"); - b.Property("Version") + b.Property("Version") .HasColumnType("INTEGER"); b.Property("Timestamp") @@ -156,11 +164,15 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("Backup") .HasColumnType("INTEGER"); + b.Property("EntityKey") + .IsRequired() + .HasColumnType("TEXT"); + b.Property("Value") .IsRequired() .HasColumnType("BLOB"); - b.Property("Version") + b.Property("Version") .HasColumnType("INTEGER"); b.HasKey("Key"); @@ -175,9 +187,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) }); 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\n INSERT INTO \"OutboxItems\" (\"Entity\", \"Version\", \"Key\", \"ActionType\") SELECT 'Setting', \r\n OLD.\"Version\" + 1, \r\n NEW.\"Key\", \r\n 1;\r\nEND;"); + .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.\"EntityKey\", \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.\"EntityKey\", \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\n INSERT INTO \"OutboxItems\" (\"Entity\", \"Version\", \"Key\", \"ActionType\") SELECT 'Setting', \r\n OLD.\"Version\" + 1, \r\n NEW.\"EntityKey\", \r\n 1;\r\nEND;"); }); modelBuilder.Entity("BTCPayApp.Core.Data.ChannelAlias", b => diff --git a/BTCPayApp.Core/Migrations/20240724071302_triggers.cs b/BTCPayApp.Core/Migrations/20240726092628_triggers.cs similarity index 83% rename from BTCPayApp.Core/Migrations/20240724071302_triggers.cs rename to BTCPayApp.Core/Migrations/20240726092628_triggers.cs index 89eac74..7ebf185 100644 --- a/BTCPayApp.Core/Migrations/20240724071302_triggers.cs +++ b/BTCPayApp.Core/Migrations/20240726092628_triggers.cs @@ -11,9 +11,10 @@ public partial class triggers : Migration /// protected override void Up(MigrationBuilder migrationBuilder) { - migrationBuilder.DropColumn( + migrationBuilder.RenameColumn( name: "Aliases", - table: "LightningChannels"); + table: "LightningChannels", + newName: "EntityKey"); migrationBuilder.AddColumn( name: "Backup", @@ -22,26 +23,40 @@ protected override void Up(MigrationBuilder migrationBuilder) nullable: false, defaultValue: false); - migrationBuilder.AddColumn( + migrationBuilder.AddColumn( + name: "EntityKey", + table: "Settings", + type: "TEXT", + nullable: false, + defaultValue: ""); + + migrationBuilder.AddColumn( name: "Version", table: "Settings", type: "INTEGER", nullable: false, - defaultValue: 0ul); + defaultValue: 0L); + + migrationBuilder.AddColumn( + name: "EntityKey", + table: "LightningPayments", + type: "TEXT", + nullable: false, + defaultValue: ""); - migrationBuilder.AddColumn( + migrationBuilder.AddColumn( name: "Version", table: "LightningPayments", type: "INTEGER", nullable: false, - defaultValue: 0ul); + defaultValue: 0L); - migrationBuilder.AddColumn( + migrationBuilder.AddColumn( name: "Version", table: "LightningChannels", type: "INTEGER", nullable: false, - defaultValue: 0ul); + defaultValue: 0L); migrationBuilder.CreateTable( name: "ChannelAliases", @@ -69,7 +84,7 @@ protected override void Up(MigrationBuilder migrationBuilder) ActionType = table.Column(type: "INTEGER", nullable: false), Key = table.Column(type: "TEXT", nullable: false), Entity = table.Column(type: "TEXT", nullable: false), - Version = table.Column(type: "INTEGER", nullable: false), + Version = table.Column(type: "INTEGER", nullable: false), Timestamp = table.Column(type: "TEXT", nullable: false, defaultValueSql: "datetime('now')") }, constraints: table => @@ -82,23 +97,23 @@ protected override void Up(MigrationBuilder migrationBuilder) table: "ChannelAliases", column: "ChannelId"); - migrationBuilder.Sql("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;"); + migrationBuilder.Sql("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.\"EntityKey\", \r\n 2;\r\nEND;"); - 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_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.\"EntityKey\", \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\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_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.\"EntityKey\", \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_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.\"EntityKey\", \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_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.\"EntityKey\", \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\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_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.\"EntityKey\", \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_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.\"EntityKey\", \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_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.\"EntityKey\", \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\n INSERT INTO \"OutboxItems\" (\"Entity\", \"Version\", \"Key\", \"ActionType\") SELECT 'Setting', \r\n OLD.\"Version\" + 1, \r\n NEW.\"Key\", \r\n 1;\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.\"EntityKey\", \r\n 1;\r\nEND;"); } /// @@ -132,10 +147,18 @@ protected override void Down(MigrationBuilder migrationBuilder) name: "Backup", table: "Settings"); + migrationBuilder.DropColumn( + name: "EntityKey", + table: "Settings"); + migrationBuilder.DropColumn( name: "Version", table: "Settings"); + migrationBuilder.DropColumn( + name: "EntityKey", + table: "LightningPayments"); + migrationBuilder.DropColumn( name: "Version", table: "LightningPayments"); @@ -144,12 +167,10 @@ protected override void Down(MigrationBuilder migrationBuilder) name: "Version", table: "LightningChannels"); - migrationBuilder.AddColumn( - name: "Aliases", + migrationBuilder.RenameColumn( + name: "EntityKey", table: "LightningChannels", - type: "TEXT", - nullable: false, - defaultValue: ""); + newName: "Aliases"); } } } diff --git a/BTCPayApp.Core/Migrations/AppDbContextModelSnapshot.cs b/BTCPayApp.Core/Migrations/AppDbContextModelSnapshot.cs index aad872f..aedeb36 100644 --- a/BTCPayApp.Core/Migrations/AppDbContextModelSnapshot.cs +++ b/BTCPayApp.Core/Migrations/AppDbContextModelSnapshot.cs @@ -32,6 +32,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("jsonb"); + b.Property("EntityKey") + .IsRequired() + .HasColumnType("TEXT"); + b.Property("PaymentRequest") .IsRequired() .HasColumnType("TEXT"); @@ -52,7 +56,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Value") .HasColumnType("INTEGER"); - b.Property("Version") + b.Property("Version") .HasColumnType("INTEGER"); b.HasKey("PaymentHash", "Inbound", "PaymentId"); @@ -67,9 +71,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) }); 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\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;"); + .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.\"EntityKey\", \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.\"EntityKey\", \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\n INSERT INTO \"OutboxItems\" (\"Entity\", \"Version\", \"Key\", \"ActionType\") SELECT 'Payment', \r\n OLD.\"Version\" + 1, \r\n NEW.\"EntityKey\", \r\n 1;\r\nEND;"); }); modelBuilder.Entity("BTCPayApp.Core.Data.Channel", b => @@ -81,7 +85,11 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("BLOB"); - b.Property("Version") + b.Property("EntityKey") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Version") .HasColumnType("INTEGER"); b.HasKey("Id"); @@ -96,9 +104,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) }); 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\n INSERT INTO \"OutboxItems\" (\"Entity\", \"Version\", \"Key\", \"ActionType\") SELECT 'Channel', \r\n OLD.\"Version\" + 1, \r\n NEW.\"Id\", \r\n 1;\r\nEND;"); + .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.\"EntityKey\", \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.\"EntityKey\", \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\n INSERT INTO \"OutboxItems\" (\"Entity\", \"Version\", \"Key\", \"ActionType\") SELECT 'Channel', \r\n OLD.\"Version\" + 1, \r\n NEW.\"EntityKey\", \r\n 1;\r\nEND;"); }); modelBuilder.Entity("BTCPayApp.Core.Data.ChannelAlias", b => @@ -132,7 +140,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("ActionType") .HasColumnType("INTEGER"); - b.Property("Version") + b.Property("Version") .HasColumnType("INTEGER"); b.Property("Timestamp") @@ -153,11 +161,15 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Backup") .HasColumnType("INTEGER"); + b.Property("EntityKey") + .IsRequired() + .HasColumnType("TEXT"); + b.Property("Value") .IsRequired() .HasColumnType("BLOB"); - b.Property("Version") + b.Property("Version") .HasColumnType("INTEGER"); b.HasKey("Key"); @@ -172,9 +184,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) }); 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\n INSERT INTO \"OutboxItems\" (\"Entity\", \"Version\", \"Key\", \"ActionType\") SELECT 'Setting', \r\n OLD.\"Version\" + 1, \r\n NEW.\"Key\", \r\n 1;\r\nEND;"); + .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.\"EntityKey\", \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.\"EntityKey\", \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\n INSERT INTO \"OutboxItems\" (\"Entity\", \"Version\", \"Key\", \"ActionType\") SELECT 'Setting', \r\n OLD.\"Version\" + 1, \r\n NEW.\"EntityKey\", \r\n 1;\r\nEND;"); }); modelBuilder.Entity("BTCPayApp.Core.Data.ChannelAlias", b => diff --git a/BTCPayApp.UI/Pages/Settings/ChangePasscodePage.razor b/BTCPayApp.UI/Pages/Settings/ChangePasscodePage.razor index 4ce9392..6899a86 100644 --- a/BTCPayApp.UI/Pages/Settings/ChangePasscodePage.razor +++ b/BTCPayApp.UI/Pages/Settings/ChangePasscodePage.razor @@ -77,7 +77,7 @@ public async Task HandleValidSubmit() { _config!.Passcode = Model.NewPasscode; - await ConfigProvider.Set(BTCPayAppConfig.Key, _config, true); + await ConfigProvider.Set(BTCPayAppConfig.Key, _config, false); NavigationManager.NavigateTo(Routes.Settings); } diff --git a/BTCPayApp.UI/Pages/Settings/IndexPage.razor b/BTCPayApp.UI/Pages/Settings/IndexPage.razor index 5a40810..ca3681f 100644 --- a/BTCPayApp.UI/Pages/Settings/IndexPage.razor +++ b/BTCPayApp.UI/Pages/Settings/IndexPage.razor @@ -209,7 +209,7 @@ if (HasPasscode) { _config!.Passcode = null; - await ConfigProvider.Set(BTCPayAppConfig.Key, _config, true); + await ConfigProvider.Set(BTCPayAppConfig.Key, _config, false); } } diff --git a/BTCPayApp.VSS/HttpVSSAPIClient.cs b/BTCPayApp.VSS/HttpVSSAPIClient.cs index ed5b153..79f232b 100644 --- a/BTCPayApp.VSS/HttpVSSAPIClient.cs +++ b/BTCPayApp.VSS/HttpVSSAPIClient.cs @@ -20,45 +20,45 @@ public HttpVSSAPIClient(Uri endpoint, HttpClient? httpClient = null) _httpClient = httpClient ?? new HttpClient(); } - public async Task GetObjectAsync(GetObjectRequest request) + public async Task GetObjectAsync(GetObjectRequest request, CancellationToken cancellationToken = default) { var url = new Uri(_endpoint, GET_OBJECT); - return await SendRequestAsync(request, url); + return await SendRequestAsync(request, url, cancellationToken); } - public async Task PutObjectAsync(PutObjectRequest request) + public async Task PutObjectAsync(PutObjectRequest request, CancellationToken cancellationToken = default) { var url = new Uri(_endpoint, PUT_OBJECTS); - return await SendRequestAsync(request, url); + return await SendRequestAsync(request, url, cancellationToken); } - public async Task DeleteObjectAsync(DeleteObjectRequest request) + public async Task DeleteObjectAsync(DeleteObjectRequest request, CancellationToken cancellationToken = default) { var url = new Uri(_endpoint, DELETE_OBJECT); - return await SendRequestAsync(request, url); + return await SendRequestAsync(request, url, cancellationToken); } - public async Task ListKeyVersionsAsync(ListKeyVersionsRequest request) + public async Task ListKeyVersionsAsync(ListKeyVersionsRequest request, CancellationToken cancellationToken = default) { var url = new Uri(_endpoint, LIST_KEY_VERSIONS); - return await SendRequestAsync(request, url); + return await SendRequestAsync(request, url, cancellationToken); } - private async Task SendRequestAsync(TRequest request, Uri url) + private async Task SendRequestAsync(TRequest request, Uri url, CancellationToken cancellationToken) where TRequest : IMessage where TResponse : IMessage, new() { var requestContent = new ByteArrayContent(request.ToByteArray()); requestContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/octet-stream"); - var response = await _httpClient.PostAsync(url, requestContent); + var response = await _httpClient.PostAsync(url, requestContent, cancellationToken); if (!response.IsSuccessStatusCode) { - var errorContent = await response.Content.ReadAsStringAsync(); + var errorContent = await response.Content.ReadAsStringAsync(cancellationToken); throw new VssClientException($"HTTP error {(int)response.StatusCode} occurred: {errorContent}"); } - var responseBytes = await response.Content.ReadAsByteArrayAsync(); + var responseBytes = await response.Content.ReadAsByteArrayAsync(cancellationToken); var parsedResponse = (TResponse)new TResponse().Descriptor.Parser.ParseFrom(responseBytes); if (parsedResponse is GetObjectResponse {Value: null}) diff --git a/BTCPayApp.VSS/IVSSAPI.cs b/BTCPayApp.VSS/IVSSAPI.cs index 1cc9152..e41faab 100644 --- a/BTCPayApp.VSS/IVSSAPI.cs +++ b/BTCPayApp.VSS/IVSSAPI.cs @@ -13,26 +13,26 @@ public interface IVSSAPI ///
/// The request details including the key to fetch. /// A task representing the asynchronous operation, which encapsulates the response with the fetched value. - Task GetObjectAsync(GetObjectRequest request); + Task GetObjectAsync(GetObjectRequest request, CancellationToken cancellationToken = default); /// /// Asynchronously writes objects as part of a single transaction to the VSS. /// /// The details of objects to be put, encapsulated as a transaction. /// A task representing the asynchronous operation, which encapsulates the response after putting objects. - Task PutObjectAsync(PutObjectRequest request); + Task PutObjectAsync(PutObjectRequest request, CancellationToken cancellationToken = default); /// /// Asynchronously deletes a specified object from the VSS. /// /// The details of the object to delete, including key and value. /// A task representing the asynchronous operation, which encapsulates the response after deleting the object. - Task DeleteObjectAsync(DeleteObjectRequest request); + Task DeleteObjectAsync(DeleteObjectRequest request, CancellationToken cancellationToken = default); /// /// Asynchronously lists all keys and their corresponding versions based on a specified store ID. /// /// The request details including the store ID for which keys and versions need to be listed. /// A task representing the asynchronous operation, which encapsulates the response with all listed keys and versions. - Task ListKeyVersionsAsync(ListKeyVersionsRequest request); + Task ListKeyVersionsAsync(ListKeyVersionsRequest request, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/BTCPayApp.VSS/VSSApiEncryptorClient.cs b/BTCPayApp.VSS/VSSApiEncryptorClient.cs index 50de59e..d4c4c8a 100644 --- a/BTCPayApp.VSS/VSSApiEncryptorClient.cs +++ b/BTCPayApp.VSS/VSSApiEncryptorClient.cs @@ -16,9 +16,9 @@ public VSSApiEncryptorClient(IVSSAPI vssApi, IDataProtector encryptor) _encryptor = encryptor; } - public async Task GetObjectAsync(GetObjectRequest request) + public async Task GetObjectAsync(GetObjectRequest request, CancellationToken cancellationToken = default) { - var response = await _vssApi.GetObjectAsync(request); + var response = await _vssApi.GetObjectAsync(request, cancellationToken); if (response.Value?.Value is null) { return response; @@ -29,7 +29,7 @@ public async Task GetObjectAsync(GetObjectRequest request) return response; } - public async Task PutObjectAsync(PutObjectRequest request) + public async Task PutObjectAsync(PutObjectRequest request, CancellationToken cancellationToken = default) { var newReq = request.Clone(); foreach (var obj in newReq.TransactionItems) @@ -39,19 +39,19 @@ public async Task PutObjectAsync(PutObjectRequest request) var encryptedValue = _encryptor.Protect(obj.Value.ToByteArray()); obj.Value = ByteString.CopyFrom(encryptedValue); } - return await _vssApi.PutObjectAsync(newReq); + return await _vssApi.PutObjectAsync(newReq, cancellationToken); } - public Task DeleteObjectAsync(DeleteObjectRequest request) + public Task DeleteObjectAsync(DeleteObjectRequest request, CancellationToken cancellationToken = default) { - return _vssApi.DeleteObjectAsync(request); + return _vssApi.DeleteObjectAsync(request, cancellationToken); } - public async Task ListKeyVersionsAsync(ListKeyVersionsRequest request) + public async Task ListKeyVersionsAsync(ListKeyVersionsRequest request, CancellationToken cancellationToken = default) { - var x = await _vssApi.ListKeyVersionsAsync(request); + var x = await _vssApi.ListKeyVersionsAsync(request, cancellationToken); foreach (var keyVersion in x.KeyVersions) { diff --git a/submodules/btcpayserver b/submodules/btcpayserver index deb9c1e..3821795 160000 --- a/submodules/btcpayserver +++ b/submodules/btcpayserver @@ -1 +1 @@ -Subproject commit deb9c1e1e1cdbb2f59225d231be09542479f0203 +Subproject commit 3821795ec5d8b2f0cb600745c2f4264534af3241 From 12ef00719e747d9f20f035c16fde036171ab2b7e Mon Sep 17 00:00:00 2001 From: Kukks Date: Fri, 26 Jul 2024 13:21:41 +0200 Subject: [PATCH 12/14] restorer --- BTCPayApp.Core/Data/OutboxProcessor.cs | 6 +- .../Data/RemoteToLocalSyncService.cs | 230 +++++++----------- BTCPayApp.Core/LDK/LDKExtensions.cs | 1 + 3 files changed, 89 insertions(+), 148 deletions(-) diff --git a/BTCPayApp.Core/Data/OutboxProcessor.cs b/BTCPayApp.Core/Data/OutboxProcessor.cs index 66ad6b8..9b6b9eb 100644 --- a/BTCPayApp.Core/Data/OutboxProcessor.cs +++ b/BTCPayApp.Core/Data/OutboxProcessor.cs @@ -1,18 +1,14 @@ -using System.Text; -using System.Text.Json; +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; public class OutboxProcessor : IScopedHostedService { diff --git a/BTCPayApp.Core/Data/RemoteToLocalSyncService.cs b/BTCPayApp.Core/Data/RemoteToLocalSyncService.cs index fcd7de4..90bc041 100644 --- a/BTCPayApp.Core/Data/RemoteToLocalSyncService.cs +++ b/BTCPayApp.Core/Data/RemoteToLocalSyncService.cs @@ -1,4 +1,5 @@ -using BTCPayApp.Core.Attempt2; +using System.Text.Json; +using BTCPayApp.Core.Attempt2; using BTCPayApp.Core.Helpers; using Microsoft.AspNetCore.DataProtection; using Microsoft.EntityFrameworkCore; @@ -10,31 +11,29 @@ public class RemoteToLocalSyncService : IScopedHostedService { private readonly IDbContextFactory _dbContextFactory; private readonly BTCPayConnectionManager _btcPayConnectionManager; - private readonly IDataProtector _dataProtector; public RemoteToLocalSyncService(IDbContextFactory dbContextFactory, - BTCPayConnectionManager btcPayConnectionManager, IDataProtectionProvider dataProtectionProvider) + BTCPayConnectionManager btcPayConnectionManager) { _dbContextFactory = dbContextFactory; _btcPayConnectionManager = btcPayConnectionManager; - _dataProtector = dataProtectionProvider.CreateProtector("RemoteToLocalSyncService"); } private async Task CreateLocalVersions(AppDbContext dbContext) { var settings = dbContext.Settings.Where(setting => setting.Backup).Select(setting => new KeyValue() { - Key = "Setting_" + setting.Key, + Key = setting.EntityKey, Version = setting.Version }); var channels = dbContext.LightningChannels.Select(channel => new KeyValue() { - Key = "Channel_" + channel.Id, + Key = channel.EntityKey, Version = channel.Version }); var payments = dbContext.LightningPayments.Select(payment => new KeyValue() { - Key = $"Payment_{payment.PaymentHash}_{payment.PaymentId}_{payment.Inbound}", + Key = payment.EntityKey, Version = payment.Version }); return await settings.Concat(channels).Concat(payments).ToArrayAsync(); @@ -49,142 +48,87 @@ public async Task Sync() await db.Database.BeginTransactionAsync(); try { - await db.Database.ExecuteSqlRawAsync("//sql to disable all triggers"); - // - // // see which ones to delete - // var toDelete = localVersions.Where(localVersion => - // remoteVersions.KeyVersions(remoteVersion => remoteVersion.Key != localVersion.Key)).ToArray(); - // - // var settingsToDelete = toDelete.Where(key => key.Key.StartsWith("Setting_")).Select(key => key.Key.Split('_')[1]); - // var channelsToDelete = toDelete.Where(key => key.Key.StartsWith("Channel_")).Select(key => key.Key.Split('_')[1]); - // var paymentsToDelete = toDelete.Where(key => key.Key.StartsWith("Payment_")).Select(key => - // { - // var keys = key.Key.Split('_').Skip(1).ToArray(); - // return (uint256.Parse(keys[0]), keys[1], bool.Parse(keys[2])); - // }); - // await db.Settings.Where(setting => settingsToDelete.Contains(setting.Key)).ExecuteDeleteAsync(); - // await db.LightningChannels.Where(channel => channelsToDelete.Contains(channel.Key)).ExecuteDeleteAsync(); - // await db.LightningPayments.Where(payment => paymentsToDelete.Contains((payment.PaymentHash, payment.PaymentId, payment.Inbound))).ExecuteDeleteAsync(); - // - // - // - // foreach (var key in toDelete) - // { - // if (key.Key.StartsWith("Setting_")) - // { - // var settingKey = key.Key.Split('_')[1]; - // var setting = await db.Settings.FindAsync(settingKey); - // if (setting != null) - // { - // db.Settings.Remove(setting); - // } - // } - // else if (key.Key.StartsWith( "Channel_")) - // { - // var channelId = key.Key.Split('_')[1]; - // var channel = await db.LightningChannels.FindAsync(channelId); - // if (channel != null) - // { - // db.LightningChannels.Remove(channel); - // } - // } - // else if (key.Key.StartsWith( "Payment_")) - // { - // var split = key.Key.Split('_'); - // var paymentHash = uint256.Parse(split[1]); - // var paymentId = split[2]; - // var inbound = bool.Parse(split[3]); - // var payment = await db.LightningPayments.FindAsync(paymentHash, paymentId, inbound); - // if (payment != null) - // { - // db.LightningPayments.Remove(payment); - // } - // } - // else - // { - // throw new ArgumentOutOfRangeException(); - // } - // } - // var toUpdate = localVersions.Where(localVersion => - // remoteVersions.KeyVersions(remoteVersion => remoteVersion.Key > localVersion.Key)).ToArray(); - // // upsert the rest when needed - // foreach (var remoteVersion in remoteVersions.KeyVersions) - // { - // var localVersion = localVersions.FirstOrDefault(localVersion => localVersion.Key == remoteVersion.Key); - // if (localVersion == null || localVersion.Version < remoteVersion.Version) - // { - // var kv = await backupApi.GetObjectAsync(new GetObjectRequest() - // { - // Key = remoteVersion.Key - // }); - // if (kv != null) - // { - // if (remoteVersion.Key.StartsWith("Setting_")) - // { - // var settingKey = remoteVersion.Key.Split('_')[1]; - // var setting = await db.Settings.FindAsync(settingKey); - // if (setting == null) - // { - // setting = new Setting() - // { - // Key = settingKey - // }; - // db.Settings.Add(setting); - // } - // - // setting.Value = kv.Value.ToByteArray(); - // setting.Version = kv.Version; - // } - // else if (remoteVersion.Key.StartsWith("Channel_")) - // { - // var channelId = remoteVersion.Key.Split('_')[1]; - // var channel = await db.LightningChannels.FindAsync(channelId); - // if (channel == null) - // { - // channel = new LightningChannel() - // { - // Id = channelId - // }; - // db.LightningChannels.Add(channel); - // } - // - // var channelData = JsonSerializer.Deserialize(kv.Value.ToStringUtf8()); - // channel.Aliases = channelData.Aliases; - // channel.Version = kv.Version; - // } - // else if (remoteVersion.Key.StartsWith("Payment_")) - // { - // var split = remoteVersion.Key.Split('_'); - // var paymentHash = uint256.Parse(split[1]); - // var paymentId = split[2]; - // var inbound = bool.Parse(split[3]); - // var payment = await db.LightningPayments.FindAsync(paymentHash, paymentId, inbound); - // if (payment == null) - // { - // payment = new LightningPayment() - // { - // PaymentHash = paymentHash, - // PaymentId = paymentId, - // Inbound = inbound - // }; - // db.LightningPayments.Add(payment); - // } - // - // var paymentData = JsonSerializer.Deserialize(kv.Value.ToStringUtf8()); - // payment.Amount = paymentData.Amount; - // payment.Bolt11 = paymentData.Bolt11; - // payment.Version = kv.Version; - // } - // else - // { - // throw new ArgumentOutOfRangeException(); - // } - // } - // } - // } - // - // - await db.Database.ExecuteSqlRawAsync("//sql to reenable all triggers"); + // Step 1: Create a temporary table for trigger definitions + await db.Database.ExecuteSqlRawAsync(""" + + CREATE TEMPORARY TABLE IF NOT EXISTS temp_triggers ( + name TEXT, + sql TEXT + ); + + """); + + // Step 2: Store existing trigger definitions in the temporary table + await db.Database.ExecuteSqlRawAsync(""" + + INSERT INTO temp_triggers (name, sql) + SELECT name, sql FROM sqlite_master WHERE type = 'trigger'; + + """); + + // Step 3: Drop all existing triggers + await db.Database.ExecuteSqlRawAsync(""" + + SELECT 'DROP TRIGGER IF EXISTS ' || name || ';' AS Command + FROM sqlite_master + WHERE type = 'trigger' + + """); + + // delete local versions that are not in remote + // delete local versions which are lower than remote + + var toDelete = localVersions.Where(localVersion => + remoteVersions.KeyVersions.All(remoteVersion => remoteVersion.Key != localVersion.Key) + || remoteVersions.KeyVersions.All(remoteVersion => remoteVersion.Key == localVersion.Key && remoteVersion.Version > localVersion.Version)).ToArray(); + + var toUpsert = remoteVersions.KeyVersions.Where(remoteVersion => localVersions.All(localVersion => localVersion.Key != remoteVersion.Key || localVersion.Version < remoteVersion.Version)); + + foreach (var upsertItem in toUpsert) + { + if(upsertItem.Value is null) + { + var item = await backupApi.GetObjectAsync(new GetObjectRequest() + { + Key = upsertItem.Key, + }); + upsertItem.MergeFrom(item.Value); + } + } + + + var settingsToDelete = toDelete.Where(key => key.Key.StartsWith("Setting_")).Select(key => key.Key); + var channelsToDelete = toDelete.Where(key => key.Key.StartsWith("Channel_")).Select(key => key.Key); + var paymentsToDelete = toDelete.Where(key => key.Key.StartsWith("Payment_")).Select(key => key.Key); + await db.Settings.Where(setting => settingsToDelete.Contains(setting.EntityKey)).ExecuteDeleteAsync(); + await db.LightningChannels.Where(channel => channelsToDelete.Contains(channel.EntityKey)).ExecuteDeleteAsync(); + await db.LightningPayments.Where(payment => paymentsToDelete.Contains(payment.EntityKey)).ExecuteDeleteAsync(); + + // upsert the rest when needed + var settingsToUpsert = toUpsert.Where(key => key.Key.StartsWith("Setting_")).Select(setting=> new Setting() + { + Key = setting.Key.Split('_')[1], + Value = setting.Value.ToByteArray(), + Version = setting.Version, + Backup = true + }); + var channelsToUpsert = toUpsert.Where(key => key.Key.StartsWith("Channel_")).Select(value => JsonSerializer.Deserialize(value.Value.ToStringUtf8())!); + var paymentsToUpsert = toUpsert.Where(key => key.Key.StartsWith("Payment_")).Select(value => JsonSerializer.Deserialize(value.Value.ToStringUtf8())!); + + await db.Settings.UpsertRange(settingsToUpsert).On(setting => setting.EntityKey).RunAsync(); + await db.LightningChannels.UpsertRange(channelsToUpsert).On(channel => channel.EntityKey).RunAsync(); + await db.LightningPayments.UpsertRange(paymentsToUpsert).On(payment => payment.EntityKey).RunAsync(); + + // Step 5: Restore triggers from the temporary table directly in SQL + await db.Database.ExecuteSqlRawAsync(""" + + INSERT INTO sqlite_master (name, sql) + SELECT name, sql FROM temp_triggers; + + """); + + // Step 6: Drop the temporary table + await db.Database.ExecuteSqlRawAsync("DROP TABLE IF EXISTS temp_triggers;"); await db.Database.CommitTransactionAsync(); } catch (Exception e) diff --git a/BTCPayApp.Core/LDK/LDKExtensions.cs b/BTCPayApp.Core/LDK/LDKExtensions.cs index c6ecbdf..1e93ce5 100644 --- a/BTCPayApp.Core/LDK/LDKExtensions.cs +++ b/BTCPayApp.Core/LDK/LDKExtensions.cs @@ -212,6 +212,7 @@ public static IServiceCollection AddLDK(this IServiceCollection services) services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); // services.AddScoped(provider => // provider.GetRequiredService()); services.AddScoped(provider => provider.GetRequiredService()); From 53fe9def9d61a29a6fdd6754f60a0ee365aa59b6 Mon Sep 17 00:00:00 2001 From: Kukks Date: Fri, 26 Jul 2024 16:03:27 +0200 Subject: [PATCH 13/14] wip --- .../Attempt2/BTCPayConnectionManager.cs | 27 ++++++-- BTCPayApp.Core/Data/AppDbContext.cs | 17 +----- BTCPayApp.Core/Data/Outbox.cs | 10 +++ BTCPayApp.Core/Data/OutboxAction.cs | 8 +++ BTCPayApp.Core/Data/OutboxProcessor.cs | 5 +- .../Data/RemoteToLocalSyncService.cs | 61 +++++-------------- ...cs => 20240726135741_triggers.Designer.cs} | 4 +- ...triggers.cs => 20240726135741_triggers.cs} | 2 +- .../Migrations/AppDbContextModelSnapshot.cs | 2 +- BTCPayApp.VSS/HttpVSSAPIClient.cs | 5 ++ submodules/btcpayserver | 2 +- 11 files changed, 67 insertions(+), 76 deletions(-) create mode 100644 BTCPayApp.Core/Data/Outbox.cs create mode 100644 BTCPayApp.Core/Data/OutboxAction.cs rename BTCPayApp.Core/Migrations/{20240726092628_triggers.Designer.cs => 20240726135741_triggers.Designer.cs} (95%) rename BTCPayApp.Core/Migrations/{20240726092628_triggers.cs => 20240726135741_triggers.cs} (96%) diff --git a/BTCPayApp.Core/Attempt2/BTCPayConnectionManager.cs b/BTCPayApp.Core/Attempt2/BTCPayConnectionManager.cs index a7c09f9..b13d681 100644 --- a/BTCPayApp.Core/Attempt2/BTCPayConnectionManager.cs +++ b/BTCPayApp.Core/Attempt2/BTCPayConnectionManager.cs @@ -1,12 +1,16 @@ using System.Net; +using System.Net.Http.Headers; using BTCPayApp.CommonServer; using BTCPayApp.Core.Auth; using BTCPayApp.Core.Contracts; +using BTCPayApp.Core.Data; using BTCPayApp.Core.Helpers; using BTCPayApp.VSS; using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.SignalR.Client; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Internal; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; @@ -34,6 +38,7 @@ public static async Task GetOrSet(this ISecureConfigProvider secureConfigP public class BTCPayConnectionManager : IHostedService, IHubConnectionObserver { + private readonly IDbContextFactory _dbContextFactory; private readonly IAccountManager _accountManager; private readonly AuthenticationStateProvider _authStateProvider; private readonly ILogger _logger; @@ -67,6 +72,7 @@ private set } public BTCPayConnectionManager( + IDbContextFactory dbContextFactory, IAccountManager accountManager, AuthenticationStateProvider authStateProvider, ILogger logger, @@ -75,6 +81,7 @@ public BTCPayConnectionManager( IHttpClientFactory httpClientFactory, ISecureConfigProvider secureConfigProvider) { + _dbContextFactory = dbContextFactory; _accountManager = accountManager; _authStateProvider = authStateProvider; _logger = logger; @@ -104,8 +111,10 @@ public async Task GetVSSAPI() { if (Connection is null) throw new InvalidOperationException("Connection is not established"); - var vssUri = new Uri(new Uri(_accountManager.GetAccount().BaseUri), "vss"); - var vssClient = new HttpVSSAPIClient(vssUri, _httpClientFactory.CreateClient("vss")); + var vssUri = new Uri(new Uri(_accountManager.GetAccount().BaseUri), "vss/"); + var httpClient = _httpClientFactory.CreateClient("vss"); + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _accountManager.GetAccount().AccessToken); + var vssClient = new HttpVSSAPIClient(vssUri, httpClient); var protector = await GetDataProtector(); return new VSSApiEncryptorClient(vssClient, protector); } @@ -142,6 +151,12 @@ private async void OnAuthenticationStateChanged(Task task) } } + private async Task MarkConnected() + { + await new RemoteToLocalSyncService(_dbContextFactory,this).Sync(); + ConnectionState = HubConnectionState.Connected; + } + private async Task TryStayConnected() { while (true) @@ -151,7 +166,8 @@ private async Task TryStayConnected() if (Connection is not null && ConnectionState == HubConnectionState.Disconnected) { await Connection.StartAsync(); - ConnectionState = HubConnectionState.Connected; + + await MarkConnected(); } else { @@ -221,11 +237,10 @@ public Task OnClosed(Exception? exception) return Task.CompletedTask; } - public Task OnReconnected(string? connectionId) + public async Task OnReconnected(string? connectionId) { _logger.LogInformation("Hub reconnected: {ConnectionId}", connectionId); - ConnectionState = HubConnectionState.Connected; - return Task.CompletedTask; + await MarkConnected(); } public Task OnReconnecting(Exception? exception) diff --git a/BTCPayApp.Core/Data/AppDbContext.cs b/BTCPayApp.Core/Data/AppDbContext.cs index b286786..1e38bf1 100644 --- a/BTCPayApp.Core/Data/AppDbContext.cs +++ b/BTCPayApp.Core/Data/AppDbContext.cs @@ -99,6 +99,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) }))) .AfterUpdate(trigger => trigger .Action(group => group + .Condition(@ref => @ref.Old.Backup) // .Condition(@ref => @ref.Old.Value != @ref.New.Value) .Update( (tableRefs, setting) => tableRefs.Old.Key == setting.Key, @@ -200,20 +201,4 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) }))); base.OnModelCreating(modelBuilder); } -} - -public enum OutboxAction -{ - Insert, - Update, - Delete -} - -public class Outbox -{ - public DateTimeOffset Timestamp { get; set; } = DateTimeOffset.Now; - public OutboxAction ActionType { get; set; } - public string Key { get; set; } - public string Entity { get; set; } - public long Version { get; set; } } \ No newline at end of file diff --git a/BTCPayApp.Core/Data/Outbox.cs b/BTCPayApp.Core/Data/Outbox.cs new file mode 100644 index 0000000..cc6cce1 --- /dev/null +++ b/BTCPayApp.Core/Data/Outbox.cs @@ -0,0 +1,10 @@ +namespace BTCPayApp.Core.Data; + +public class Outbox +{ + public DateTimeOffset Timestamp { get; set; } = DateTimeOffset.Now; + public OutboxAction ActionType { get; set; } + public string Key { get; set; } + public string Entity { get; set; } + public long Version { get; set; } +} \ No newline at end of file diff --git a/BTCPayApp.Core/Data/OutboxAction.cs b/BTCPayApp.Core/Data/OutboxAction.cs new file mode 100644 index 0000000..94bb742 --- /dev/null +++ b/BTCPayApp.Core/Data/OutboxAction.cs @@ -0,0 +1,8 @@ +namespace BTCPayApp.Core.Data; + +public enum OutboxAction +{ + Insert, + Update, + Delete +} \ No newline at end of file diff --git a/BTCPayApp.Core/Data/OutboxProcessor.cs b/BTCPayApp.Core/Data/OutboxProcessor.cs index 9b6b9eb..ee85217 100644 --- a/BTCPayApp.Core/Data/OutboxProcessor.cs +++ b/BTCPayApp.Core/Data/OutboxProcessor.cs @@ -2,8 +2,8 @@ using BTCPayApp.Core.Attempt2; using BTCPayApp.Core.Helpers; using Google.Protobuf; -using Microsoft.EntityFrameworkCore; using VSSProto; +using Microsoft.EntityFrameworkCore; namespace BTCPayApp.Core.Data; @@ -119,7 +119,7 @@ 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}) + var outbox = await db.OutboxItems.GroupBy(outbox1 => new {outbox1.Key}) .ToListAsync(cancellationToken: cancellationToken); foreach (var outboxItemSet in outbox) { @@ -150,6 +150,7 @@ private async Task ProcessOutbox(CancellationToken cancellationToken = default) } await backupAPi.PutObjectAsync(putObjectRequest, cancellationToken); + await db.SaveChangesAsync(cancellationToken); } private CancellationTokenSource _cts = new(); diff --git a/BTCPayApp.Core/Data/RemoteToLocalSyncService.cs b/BTCPayApp.Core/Data/RemoteToLocalSyncService.cs index 90bc041..511647f 100644 --- a/BTCPayApp.Core/Data/RemoteToLocalSyncService.cs +++ b/BTCPayApp.Core/Data/RemoteToLocalSyncService.cs @@ -1,4 +1,5 @@ -using System.Text.Json; +using System.Security.Cryptography.X509Certificates; +using System.Text.Json; using BTCPayApp.Core.Attempt2; using BTCPayApp.Core.Helpers; using Microsoft.AspNetCore.DataProtection; @@ -7,7 +8,12 @@ namespace BTCPayApp.Core.Data; -public class RemoteToLocalSyncService : IScopedHostedService +class TriggerRecord +{ + public string name { get; set; } + public string sql { get; set; } +} +public class RemoteToLocalSyncService { private readonly IDbContextFactory _dbContextFactory; private readonly BTCPayConnectionManager _btcPayConnectionManager; @@ -48,32 +54,10 @@ public async Task Sync() await db.Database.BeginTransactionAsync(); try { - // Step 1: Create a temporary table for trigger definitions - await db.Database.ExecuteSqlRawAsync(""" - - CREATE TEMPORARY TABLE IF NOT EXISTS temp_triggers ( - name TEXT, - sql TEXT - ); - - """); - - // Step 2: Store existing trigger definitions in the temporary table - await db.Database.ExecuteSqlRawAsync(""" - - INSERT INTO temp_triggers (name, sql) - SELECT name, sql FROM sqlite_master WHERE type = 'trigger'; - - """); - - // Step 3: Drop all existing triggers - await db.Database.ExecuteSqlRawAsync(""" - - SELECT 'DROP TRIGGER IF EXISTS ' || name || ';' AS Command - FROM sqlite_master - WHERE type = 'trigger' - - """); + + + var triggers = await db.Database.SqlQuery($"SELECT name, sql FROM sqlite_master WHERE type = 'trigger'").ToListAsync(); + await db.Database.ExecuteSqlRawAsync( string.Join("; ", triggers.Select(trigger => $"DROP TRIGGER IF EXISTS {trigger.name}"))); // delete local versions that are not in remote // delete local versions which are lower than remote @@ -119,17 +103,9 @@ FROM sqlite_master await db.LightningChannels.UpsertRange(channelsToUpsert).On(channel => channel.EntityKey).RunAsync(); await db.LightningPayments.UpsertRange(paymentsToUpsert).On(payment => payment.EntityKey).RunAsync(); - // Step 5: Restore triggers from the temporary table directly in SQL - await db.Database.ExecuteSqlRawAsync(""" - - INSERT INTO sqlite_master (name, sql) - SELECT name, sql FROM temp_triggers; - - """); - - // Step 6: Drop the temporary table - await db.Database.ExecuteSqlRawAsync("DROP TABLE IF EXISTS temp_triggers;"); + await db.Database.ExecuteSqlRawAsync(string.Join("; ", triggers.Select(record => record.sql))); await db.Database.CommitTransactionAsync(); + await db.SaveChangesAsync(); } catch (Exception e) { @@ -138,13 +114,4 @@ INSERT INTO sqlite_master (name, sql) } } - public async Task StartAsync(CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - public async Task StopAsync(CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } } \ No newline at end of file diff --git a/BTCPayApp.Core/Migrations/20240726092628_triggers.Designer.cs b/BTCPayApp.Core/Migrations/20240726135741_triggers.Designer.cs similarity index 95% rename from BTCPayApp.Core/Migrations/20240726092628_triggers.Designer.cs rename to BTCPayApp.Core/Migrations/20240726135741_triggers.Designer.cs index 20061b2..5630cde 100644 --- a/BTCPayApp.Core/Migrations/20240726092628_triggers.Designer.cs +++ b/BTCPayApp.Core/Migrations/20240726135741_triggers.Designer.cs @@ -11,7 +11,7 @@ namespace BTCPayApp.Core.Migrations { [DbContext(typeof(AppDbContext))] - [Migration("20240726092628_triggers")] + [Migration("20240726135741_triggers")] partial class triggers { /// @@ -189,7 +189,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) 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.\"EntityKey\", \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.\"EntityKey\", \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\n INSERT INTO \"OutboxItems\" (\"Entity\", \"Version\", \"Key\", \"ActionType\") SELECT 'Setting', \r\n OLD.\"Version\" + 1, \r\n NEW.\"EntityKey\", \r\n 1;\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\nWHEN \r\n \r\n OLD.\"Backup\" IS TRUE\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.\"EntityKey\", \r\n 1;\r\nEND;"); }); modelBuilder.Entity("BTCPayApp.Core.Data.ChannelAlias", b => diff --git a/BTCPayApp.Core/Migrations/20240726092628_triggers.cs b/BTCPayApp.Core/Migrations/20240726135741_triggers.cs similarity index 96% rename from BTCPayApp.Core/Migrations/20240726092628_triggers.cs rename to BTCPayApp.Core/Migrations/20240726135741_triggers.cs index 7ebf185..65291b3 100644 --- a/BTCPayApp.Core/Migrations/20240726092628_triggers.cs +++ b/BTCPayApp.Core/Migrations/20240726135741_triggers.cs @@ -113,7 +113,7 @@ protected override void Up(MigrationBuilder migrationBuilder) 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.\"EntityKey\", \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\n INSERT INTO \"OutboxItems\" (\"Entity\", \"Version\", \"Key\", \"ActionType\") SELECT 'Setting', \r\n OLD.\"Version\" + 1, \r\n NEW.\"EntityKey\", \r\n 1;\r\nEND;"); + migrationBuilder.Sql("CREATE TRIGGER LC_TRIGGER_AFTER_UPDATE_SETTING\r\nAFTER UPDATE ON \"Settings\"\r\nFOR EACH ROW\r\nWHEN \r\n \r\n OLD.\"Backup\" IS TRUE\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.\"EntityKey\", \r\n 1;\r\nEND;"); } /// diff --git a/BTCPayApp.Core/Migrations/AppDbContextModelSnapshot.cs b/BTCPayApp.Core/Migrations/AppDbContextModelSnapshot.cs index aedeb36..5aa4ee0 100644 --- a/BTCPayApp.Core/Migrations/AppDbContextModelSnapshot.cs +++ b/BTCPayApp.Core/Migrations/AppDbContextModelSnapshot.cs @@ -186,7 +186,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) 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.\"EntityKey\", \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.\"EntityKey\", \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\n INSERT INTO \"OutboxItems\" (\"Entity\", \"Version\", \"Key\", \"ActionType\") SELECT 'Setting', \r\n OLD.\"Version\" + 1, \r\n NEW.\"EntityKey\", \r\n 1;\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\nWHEN \r\n \r\n OLD.\"Backup\" IS TRUE\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.\"EntityKey\", \r\n 1;\r\nEND;"); }); modelBuilder.Entity("BTCPayApp.Core.Data.ChannelAlias", b => diff --git a/BTCPayApp.VSS/HttpVSSAPIClient.cs b/BTCPayApp.VSS/HttpVSSAPIClient.cs index 79f232b..32bdbbf 100644 --- a/BTCPayApp.VSS/HttpVSSAPIClient.cs +++ b/BTCPayApp.VSS/HttpVSSAPIClient.cs @@ -50,6 +50,11 @@ private async Task SendRequestAsync(TRequest req { var requestContent = new ByteArrayContent(request.ToByteArray()); requestContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/octet-stream"); + foreach (var (key, value) in _httpClient.DefaultRequestHeaders) + { + requestContent.Headers.TryAddWithoutValidation(key, value); + + } var response = await _httpClient.PostAsync(url, requestContent, cancellationToken); if (!response.IsSuccessStatusCode) diff --git a/submodules/btcpayserver b/submodules/btcpayserver index 3821795..a6c01f4 160000 --- a/submodules/btcpayserver +++ b/submodules/btcpayserver @@ -1 +1 @@ -Subproject commit 3821795ec5d8b2f0cb600745c2f4264534af3241 +Subproject commit a6c01f46581d7a94c5cc621dd46873793ff5f5f6 From e6ee0d8a2fe14eb8dd0a183f45ae3137bc151ab9 Mon Sep 17 00:00:00 2001 From: Kukks Date: Mon, 29 Jul 2024 13:37:49 +0200 Subject: [PATCH 14/14] do not use syncer for now --- BTCPayApp.Core/Attempt2/BTCPayConnectionManager.cs | 2 +- submodules/btcpayserver | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/BTCPayApp.Core/Attempt2/BTCPayConnectionManager.cs b/BTCPayApp.Core/Attempt2/BTCPayConnectionManager.cs index b13d681..44ae17e 100644 --- a/BTCPayApp.Core/Attempt2/BTCPayConnectionManager.cs +++ b/BTCPayApp.Core/Attempt2/BTCPayConnectionManager.cs @@ -153,7 +153,7 @@ private async void OnAuthenticationStateChanged(Task task) private async Task MarkConnected() { - await new RemoteToLocalSyncService(_dbContextFactory,this).Sync(); + // await new RemoteToLocalSyncService(_dbContextFactory,this).Sync(); ConnectionState = HubConnectionState.Connected; } diff --git a/submodules/btcpayserver b/submodules/btcpayserver index a6c01f4..0df173b 160000 --- a/submodules/btcpayserver +++ b/submodules/btcpayserver @@ -1 +1 @@ -Subproject commit a6c01f46581d7a94c5cc621dd46873793ff5f5f6 +Subproject commit 0df173b415068e2117d0d67fc2a528b9b83afe40