Skip to content

Commit

Permalink
JIT + Server Backup + JSON refactor (#69)
Browse files Browse the repository at this point in the history
  • Loading branch information
Kukks authored Jul 29, 2024
1 parent 209b799 commit 05164ec
Show file tree
Hide file tree
Showing 70 changed files with 2,625 additions and 911 deletions.
46 changes: 46 additions & 0 deletions BTCPayApp.Core/Attempt2/AppToServerHelper.cs
Original file line number Diff line number Diff line change
@@ -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<List<LightningPayment>> ToPayments(this Task<List<AppLightningPayment>> appLightningPayments)
{
var result = await appLightningPayments;
return result.Select(ToPayment).ToList();
}
public static async Task<List<LightningInvoice>> ToInvoices(this Task<List<AppLightningPayment>> appLightningPayments)
{
var result = await appLightningPayments;
return result.Select(ToInvoice).ToList();
}
}
72 changes: 35 additions & 37 deletions BTCPayApp.Core/Attempt2/BTCPayAppServerClient.cs
Original file line number Diff line number Diff line change
@@ -1,118 +1,116 @@
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;
using Microsoft.Extensions.Logging;
using NBitcoin;
using NBitcoin.Crypto;
using LightningPayment = BTCPayApp.CommonServer.Models.LightningPayment;

namespace BTCPayApp.Core.Attempt2;

public class BTCPayAppServerClient(ILogger<BTCPayAppServerClient> logger, IServiceProvider serviceProvider) : IBTCPayAppHubClient
public class BTCPayAppServerClient(ILogger<BTCPayAppServerClient> _logger, IServiceProvider _serviceProvider) : IBTCPayAppHubClient
{
public event AsyncEventHandler<string>? OnNewBlock;
public event AsyncEventHandler<TransactionDetectedRequest>? OnTransactionDetected;
public event AsyncEventHandler<string>? OnNotifyNetwork;
public event AsyncEventHandler<string>? OnServerNodeInfo;
public event AsyncEventHandler<ServerEvent>? OnNotifyServerEvent;

public async Task NotifyServerEvent(ServerEvent serverEvent)
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)
{
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<LightningNodeManager>().Node.PaymentsManager;
_serviceProvider.GetRequiredService<LightningNodeManager>().Node.PaymentsManager;

public async Task<LightningPayment> CreateInvoice(CreateLightningInvoiceRequest createLightningInvoiceRequest)
public async Task<LightningInvoice> 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<LightningPayment?> GetLightningInvoice(string paymentHash)
public async Task<LightningInvoice?> 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<LightningPayment?> GetLightningPayment(string paymentHash)
public async Task<LightningPayment?> 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<List<LightningPayment>> 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<List<LightningPayment>> GetLightningInvoices(ListInvoicesParams request)
public async Task<List<LightningInvoice>> 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<PayResponse> PayInvoice(string bolt11, long? amountMilliSatoshi)
{
var network = serviceProvider.GetRequiredService<OnChainWalletManager>().Network;
var network = _serviceProvider.GetRequiredService<OnChainWalletManager>().Network;
var bolt = BOLT11PaymentRequest.Parse(bolt11, network);
try
{
var result = await PaymentsManager.PayInvoice(bolt,
amountMilliSatoshi is null ? null : LightMoney.MilliSatoshis(amountMilliSatoshi.Value));
return new PayResponse()
{
Result = result.Status switch
{
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
}
};
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
}
};
}
catch (Exception e)
{
logger.LogError(e, "Error paying invoice");
_logger.LogError(e, "Error paying invoice");
return new PayResponse(PayResult.Error, e.Message);
}
}
Expand Down
73 changes: 67 additions & 6 deletions BTCPayApp.Core/Attempt2/BTCPayConnectionManager.cs
Original file line number Diff line number Diff line change
@@ -1,9 +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;
Expand All @@ -12,13 +19,33 @@

namespace BTCPayApp.Core.Attempt2;

public static class ConfigHelpers
{

public static async Task<T> GetOrSet<T>(this ISecureConfigProvider secureConfigProvider, string key, Func<Task<T>> factory)
{
var value = await secureConfigProvider.Get<T>(key);
if (value is null)
{
value = await factory();
await secureConfigProvider.Set(key, value);
}

return value;

}
}

public class BTCPayConnectionManager : IHostedService, IHubConnectionObserver
{
private readonly IDbContextFactory<AppDbContext> _dbContextFactory;
private readonly IAccountManager _accountManager;
private readonly AuthenticationStateProvider _authStateProvider;
private readonly ILogger<BTCPayConnectionManager> _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; }
Expand All @@ -45,17 +72,23 @@ private set
}

public BTCPayConnectionManager(
IDbContextFactory<AppDbContext> dbContextFactory,
IAccountManager accountManager,
AuthenticationStateProvider authStateProvider,
ILogger<BTCPayConnectionManager> logger,
BTCPayAppServerClient btcPayAppServerClient,
IBTCPayAppHubClient btcPayAppServerClientInterface)
IBTCPayAppHubClient btcPayAppServerClientInterface,
IHttpClientFactory httpClientFactory,
ISecureConfigProvider secureConfigProvider)
{
_dbContextFactory = dbContextFactory;
_accountManager = accountManager;
_authStateProvider = authStateProvider;
_logger = logger;
_btcPayAppServerClient = btcPayAppServerClient;
_btcPayAppServerClientInterface = btcPayAppServerClientInterface;
_httpClientFactory = httpClientFactory;
_secureConfigProvider = secureConfigProvider;
}

public async Task StartAsync(CancellationToken cancellationToken)
Expand All @@ -67,7 +100,25 @@ public async Task StartAsync(CancellationToken cancellationToken)
await StartOrReplace();
_ = TryStayConnected();
}

private async Task<IDataProtector> GetDataProtector()
{
var key = await _secureConfigProvider.GetOrSet("encryptionKey", async () => Convert.ToHexString(RandomUtils.GetBytes(32)).ToLowerInvariant());
return new SingleKeyDataProtector(Convert.FromHexString(key));
}

public async Task<IVSSAPI> GetVSSAPI()
{
if (Connection is null)
throw new InvalidOperationException("Connection is not established");
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);
}

private async Task OnServerNodeInfo(object? sender, string e)
{
ReportedNodeInfo = e;
Expand Down Expand Up @@ -100,6 +151,12 @@ private async void OnAuthenticationStateChanged(Task<AuthenticationState> task)
}
}

private async Task MarkConnected()
{
// await new RemoteToLocalSyncService(_dbContextFactory,this).Sync();
ConnectionState = HubConnectionState.Connected;
}

private async Task TryStayConnected()
{
while (true)
Expand All @@ -109,7 +166,8 @@ private async Task TryStayConnected()
if (Connection is not null && ConnectionState == HubConnectionState.Disconnected)
{
await Connection.StartAsync();
ConnectionState = HubConnectionState.Connected;

await MarkConnected();
}
else
{
Expand Down Expand Up @@ -148,8 +206,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);
Expand All @@ -175,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)
Expand Down
13 changes: 7 additions & 6 deletions BTCPayApp.Core/Attempt2/BTCPayPaymentsNotifier.cs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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;
Expand Down
Loading

0 comments on commit 05164ec

Please sign in to comment.