Skip to content

Commit

Permalink
update and refactor to remove race conditions. Add API keys to paymen…
Browse files Browse the repository at this point in the history
…t method for app ln

# Conflicts:
#	submodules/btcpayserver
  • Loading branch information
Kukks committed Nov 4, 2024
1 parent 3eef655 commit 5587440
Show file tree
Hide file tree
Showing 41 changed files with 663 additions and 284 deletions.
8 changes: 0 additions & 8 deletions .run/DEV ALL mutiny.run.xml

This file was deleted.

19 changes: 10 additions & 9 deletions BTCPayApp.Core/Auth/AuthStateProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ namespace BTCPayApp.Core.Auth;

public class AuthStateProvider(
IHttpClientFactory clientFactory,
IConfigProvider config,
ConfigProvider configProvider,
IAuthorizationService authService,
IOptionsMonitor<IdentityOptions> identityOptions)
: AuthenticationStateProvider, IAccountManager, IHostedService
Expand Down Expand Up @@ -115,6 +115,7 @@ public override async Task<AuthenticationState> GetAuthenticationStateAsync()
var res = new AuthenticationState(user);
if (AppUserInfo.Equals(oldUserInfo, _userInfo)) return res;

//TODO: should this check against old user info?s
if (_userInfo != null)
{
OnUserInfoChange?.Invoke(this, _userInfo);
Expand Down Expand Up @@ -430,45 +431,45 @@ private async void OnAccessRefresh(object? sender, AccessTokenResult access)
public async Task<IEnumerable<BTCPayAccount>> GetAccounts(string? hostFilter = null)
{
var prefix = $"{AccountKeyPrefix}:" + (hostFilter == null ? "" : $"{hostFilter}:");
var keys = (await config.List(prefix)).ToArray();
var keys = (await configProvider.List(prefix)).ToArray();
var accounts = new List<BTCPayAccount>();
foreach (var key in keys)
{
var account = await config.Get<BTCPayAccount>(key);
var account = await configProvider.Get<BTCPayAccount>(key);
accounts.Add(account!);
}
return accounts;
}

public async Task UpdateAccount(BTCPayAccount account)
{
await config.Set(GetKey(account.Id), account, false);
await configProvider.Set(GetKey(account.Id), account, false);
}

public async Task RemoveAccount(BTCPayAccount account)
{
await config.Set<BTCPayAccount>(GetKey(account.Id), null, false);
await configProvider.Set<BTCPayAccount>(GetKey(account.Id), null, false);
}

private async Task<BTCPayAccount> GetAccount(string serverUrl, string email)
{
var accountId = BTCPayAccount.GetId(serverUrl, email);
var account = await config.Get<BTCPayAccount>(GetKey(accountId));
var account = await configProvider.Get<BTCPayAccount>(GetKey(accountId));
return account ?? new BTCPayAccount(serverUrl, email);
}

private async Task<BTCPayAccount?> GetCurrentAccount()
{
var accountId = await config.Get<string>(CurrentAccountKey);
var accountId = await configProvider.Get<string>(CurrentAccountKey);
if (string.IsNullOrEmpty(accountId)) return null;
return await config.Get<BTCPayAccount>(GetKey(accountId));
return await configProvider.Get<BTCPayAccount>(GetKey(accountId));
}

private async Task SetCurrentAccount(BTCPayAccount? account)
{
OnBeforeAccountChange?.Invoke(this, _account);
if (account != null) await UpdateAccount(account);
await config.Set(CurrentAccountKey, account?.Id, false);
await configProvider.Set(CurrentAccountKey, account?.Id, false);
_account = account;
_userInfo = null;

Expand Down
57 changes: 46 additions & 11 deletions BTCPayApp.Core/BTCPayServer/BTCPayAppServerClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
using BTCPayApp.Core.Wallet;
using BTCPayServer.Client.Models;
using BTCPayServer.Lightning;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using NBitcoin;
using NBitcoin.Crypto;

namespace BTCPayApp.Core.BTCPayServer;


public class BTCPayAppServerClient(ILogger<BTCPayAppServerClient> _logger, IServiceProvider _serviceProvider)
: IBTCPayAppHubClient
{
Expand Down Expand Up @@ -52,51 +54,81 @@ public async Task NewBlock(string block)
await OnNewBlock?.Invoke(this, block);
}

public async Task StartListen(string key)
{
await AssertPermission(key, APIKeyPermission.Read);
_serviceProvider
.GetRequiredService<LightningNodeManager>().Node
.GetServiceProvider()
.GetRequiredService<BTCPayPaymentsNotifier>()
.StartListen();
}

private PaymentsManager PaymentsManager =>
_serviceProvider.GetRequiredService<LightningNodeManager>().Node.PaymentsManager;
private LightningAPIKeyManager ApiKeyManager =>
_serviceProvider.GetRequiredService<LightningNodeManager>().Node.ApiKeyManager;

public async Task<LightningInvoice> CreateInvoice(CreateLightningInvoiceRequest createLightningInvoiceRequest)
private async Task AssertPermission(string key, APIKeyPermission permission)
{
if (!await ApiKeyManager.CheckPermission(key, permission))
{
throw new HubException("Permission denied");
}
}

public async Task<LightningInvoice> CreateInvoice(string key, CreateLightningInvoiceRequest createLightningInvoiceRequest)
{
await AssertPermission(key, APIKeyPermission.Read);
var descHash = new uint256(Hashes.SHA256(Encoding.UTF8.GetBytes(createLightningInvoiceRequest.Description)),
false);
return (await PaymentsManager.RequestPayment(createLightningInvoiceRequest.Amount,
createLightningInvoiceRequest.Expiry, descHash)).ToInvoice();
}

public async Task<LightningInvoice?> GetLightningInvoice(uint256 paymentHash)
public async Task<LightningInvoice?> GetLightningInvoice(string key, uint256 paymentHash)
{

await AssertPermission(key, APIKeyPermission.Read);
var invs = await PaymentsManager.List(payments =>
payments.Where(payment => payment.Inbound && payment.PaymentHash == paymentHash));
return invs.FirstOrDefault()?.ToInvoice();
}

public async Task<LightningPayment?> GetLightningPayment(uint256 paymentHash)
public async Task<LightningPayment?> GetLightningPayment(string key, uint256 paymentHash)
{
await AssertPermission(key, APIKeyPermission.Read);
var invs = await PaymentsManager.List(payments =>
payments.Where(payment => !payment.Inbound && payment.PaymentHash == paymentHash));
return invs.FirstOrDefault()?.ToPayment();
}

public async Task CancelInvoice(uint256 paymentHash)
public async Task CancelInvoice(string key, uint256 paymentHash)
{
await AssertPermission(key, APIKeyPermission.Write);
await PaymentsManager.CancelInbound(paymentHash);
}

public async Task<List<LightningPayment>> GetLightningPayments(ListPaymentsParams request)
public async Task<List<LightningPayment>> GetLightningPayments(string key, ListPaymentsParams request)
{

await AssertPermission(key, APIKeyPermission.Read);
return await PaymentsManager.List(payments => payments.Where(payment => !payment.Inbound), default)
.ToPayments();
}

public async Task<List<LightningInvoice>> GetLightningInvoices(ListInvoicesParams request)
public async Task<List<LightningInvoice>> GetLightningInvoices(string key, ListInvoicesParams request)
{
await AssertPermission(key, APIKeyPermission.Read);
return await PaymentsManager.List(payments => payments.Where(payment => payment.Inbound), default).ToInvoices();
}

public async Task<PayResponse> PayInvoice(string bolt11, long? amountMilliSatoshi)
public async Task<PayResponse> PayInvoice(string key, string bolt11, long? amountMilliSatoshi)
{
var network = _serviceProvider.GetRequiredService<OnChainWalletManager>().Network;
var bolt = BOLT11PaymentRequest.Parse(bolt11, network);

await AssertPermission(key, APIKeyPermission.Write);
var config = await _serviceProvider.GetRequiredService<OnChainWalletManager>().GetConfig();
var bolt = BOLT11PaymentRequest.Parse(bolt11, config.NBitcoinNetwork);
try
{
var result = await PaymentsManager.PayInvoice(bolt,
Expand Down Expand Up @@ -131,8 +163,10 @@ public async Task MasterUpdated(long? deviceIdentifier)
OnMasterUpdated?.Invoke(this, deviceIdentifier);
}

public async Task<LightningNodeInformation> GetLightningNodeInfo()
public async Task<LightningNodeInformation> GetLightningNodeInfo(string key)
{

await AssertPermission(key, APIKeyPermission.Read);
var node = _serviceProvider.GetRequiredService<LightningNodeManager>().Node;
var bb = await _serviceProvider.GetRequiredService<OnChainWalletManager>().GetBestBlock();
var config = await node.GetConfig();
Expand All @@ -154,8 +188,9 @@ public async Task<LightningNodeInformation> GetLightningNodeInfo()
};
}

public async Task<LightningNodeBalance> GetLightningBalance()
public async Task<LightningNodeBalance> GetLightningBalance(string key)
{
await AssertPermission(key, APIKeyPermission.Read);
var channels = (await _serviceProvider.GetRequiredService<LightningNodeManager>().Node.GetChannels())
.Where(channel => channel.Value.channelDetails is not null).Select(channel => channel.Value.channelDetails)
.ToArray();
Expand Down
4 changes: 2 additions & 2 deletions BTCPayApp.Core/BTCPayServer/BTCPayConnectionManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public class BTCPayConnectionManager : BaseHostedService, IHubConnectionObserver
private readonly ILogger<BTCPayConnectionManager> _logger;
private readonly BTCPayAppServerClient _btcPayAppServerClient;
private readonly IBTCPayAppHubClient _btcPayAppServerClientInterface;
private readonly IConfigProvider _configProvider;
private readonly ConfigProvider _configProvider;
private readonly SyncService _syncService;
private IDisposable? _subscription;

Expand Down Expand Up @@ -73,7 +73,7 @@ public BTCPayConnectionManager(
ILogger<BTCPayConnectionManager> logger,
BTCPayAppServerClient btcPayAppServerClient,
IBTCPayAppHubClient btcPayAppServerClientInterface,
IConfigProvider configProvider,
ConfigProvider configProvider,
SyncService syncService) : base(logger)
{
_serviceProvider = serviceProvider;
Expand Down
16 changes: 16 additions & 0 deletions BTCPayApp.Core/BTCPayServer/BTCPayPaymentsNotifier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,20 @@ public BTCPayPaymentsNotifier(
public async Task StartAsync(CancellationToken cancellationToken)
{
_paymentsManager.OnPaymentUpdate += OnPaymentUpdate;
_connectionManager.ConnectionChanged += ConnectionManagerOnConnectionChanged;
}
private bool _listening = false;

private Task ConnectionManagerOnConnectionChanged(object? sender, (BTCPayConnectionState Old, BTCPayConnectionState New) e)
{
_listening = false;
return Task.CompletedTask;
}

private async Task OnPaymentUpdate(object? sender, AppLightningPayment e)
{
if (!_listening)
return;
await _connectionManager.HubProxy
.SendInvoiceUpdate(e.ToInvoice());
}
Expand All @@ -33,4 +43,10 @@ public async Task StopAsync(CancellationToken cancellationToken)
{
_paymentsManager.OnPaymentUpdate -= OnPaymentUpdate;
}

public void StartListen()
{
_listening = true;

}
}
23 changes: 19 additions & 4 deletions BTCPayApp.Core/Backup/SyncService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,20 @@ namespace BTCPayApp.Core.Backup;

public class SyncService : IDisposable
{
private readonly IConfigProvider _configProvider;
private readonly ConfigProvider _configProvider;
private readonly ILogger<SyncService> _logger;
private readonly IAccountManager _accountManager;
private readonly IHttpClientFactory _httpClientFactory;
private readonly IDbContextFactory<AppDbContext> _dbContextFactory;
private readonly ISecureConfigProvider _secureConfigProvider;
public AsyncEventHandler? EncryptionKeyChanged;
public AsyncEventHandler<(List<Outbox> OutboxItemsProcesed, PutObjectRequest RemoteRequest)>? RemoteObjectUpdated;
public AsyncEventHandler<string[]>? LocalUpdated;

private (Task syncTask, CancellationTokenSource cts, bool local)? _syncTask;

public SyncService(
IConfigProvider configProvider,
ConfigProvider configProvider,
ILogger<SyncService> logger,
ISecureConfigProvider secureConfigProvider,
IAccountManager accountManager,
Expand Down Expand Up @@ -258,7 +260,7 @@ await db.Database.ExecuteSqlRawAsync(
Value = setting.Value.ToByteArray(),
Version = setting.Version,
Backup = true
});
}).ToArray();
var channelsToUpsert = toUpsert.Where(key => key.Key.StartsWith("Channel_"))
.Select(value => JsonSerializer.Deserialize<Channel>(value.Value.ToStringUtf8())!);
var paymentsToUpsert = toUpsert.Where(key => key.Key.StartsWith("Payment_")).Select(value =>
Expand All @@ -277,6 +279,9 @@ await db.Database.ExecuteSqlRawAsync(
await db.Database.CommitTransactionAsync(cancellationToken);
_logger.LogInformation("Synced to local: {DeleteCount} deleted, {UpsertCount} upserted", deleteCount,
upsertCount);
LocalUpdated?.Invoke(this, toDelete.Concat(toUpsert).Select(key => key.Key).ToArray());
settingsToUpsert.Select(setting => setting.Key).Concat(settingsToDelete).Distinct().ToList()
.ForEach(key => _configProvider.Updated?.Invoke(this, key));
}
catch (Exception e)
{
Expand Down Expand Up @@ -334,8 +339,13 @@ await db.Database.ExecuteSqlRawAsync(
}
}

private SemaphoreSlim _syncLock = new(1, 1);
public async Task SyncToRemote(CancellationToken cancellationToken = default)
{
try
{
await _syncLock.WaitAsync(cancellationToken);

var backupAPi = await GetVSSAPI();
if (backupAPi is null)
return;
Expand Down Expand Up @@ -393,7 +403,11 @@ public async Task SyncToRemote(CancellationToken cancellationToken = default)
$"Synced to remote {putObjectRequest.TransactionItems.Count} items and deleted {putObjectRequest.DeleteItems.Count} items" +
string.Join(", ", putObjectRequest.TransactionItems.Select(kv => kv.Key + " " + kv.Version)));
RemoteObjectUpdated?.Invoke(this, (removedOutboxItems, putObjectRequest.Clone()));

}
finally
{
_syncLock.Release();
}
}
public async Task StartSync(bool local,CancellationToken cancellationToken = default)
{
Expand Down Expand Up @@ -446,5 +460,6 @@ public void Dispose()
{
RemoteObjectUpdated = null;
EncryptionKeyChanged = null;
LocalUpdated = null;
}
}
18 changes: 13 additions & 5 deletions BTCPayApp.Core/Contracts/IConfigProvider.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
namespace BTCPayApp.Core.Contracts;
using BTCPayApp.Core.Helpers;

public interface IConfigProvider
namespace BTCPayApp.Core.Contracts;

public abstract class ConfigProvider : IDisposable
{
Task<T?> Get<T>(string key);
Task Set<T>(string key, T? value, bool backup);
Task<IEnumerable<string>> List(string prefix);
public abstract Task<T?> Get<T>(string key);
public abstract Task Set<T>(string key, T? value, bool backup);
public abstract Task<IEnumerable<string>> List(string prefix);
public AsyncEventHandler<string>? Updated;

public virtual void Dispose()
{
Updated = null;
}
}
Loading

0 comments on commit 5587440

Please sign in to comment.