Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JIT + Server Backup + JSON refactor #69

Merged
merged 17 commits into from
Jul 29, 2024
Merged
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
Loading