Fix server event communication

This commit is contained in:
Dennis Reimann 2024-07-09 14:35:18 +02:00
parent d18e8d2eaf
commit 8017493ff5
No known key found for this signature in database
GPG key ID: 5009E1797F03F8D0
12 changed files with 82 additions and 109 deletions

View file

@ -3,26 +3,14 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using BTCPayServer.Client.Models;
using BTCPayServer.Lightning;
using NBitcoin;
using LightningPayment = BTCPayApp.CommonServer.Models.LightningPayment;
namespace BTCPayApp.CommonServer;
public class TransactionDetectedRequest
{
public string Identifier { get; set; }
public string TxId { get; set; }
public string[] SpentScripts { get; set; }
public string[] ReceivedScripts { get; set; }
public bool Confirmed { get; set; }
}
//methods available on the hub in the client
public interface IBTCPayAppHubClient
{
Task NotifyServerEvent(IServerEvent ev);
Task NotifyServerEvent(ServerEvent ev);
Task NotifyNetwork(string network);
Task NotifyServerNode(string nodeInfo);
Task TransactionDetected(TransactionDetectedRequest request);
@ -36,6 +24,28 @@ public interface IBTCPayAppHubClient
Task<PayResponse> PayInvoice(string bolt11, long? amountMilliSatoshi);
}
//methods available on the hub in the server
public interface IBTCPayAppHubServer
{
Task<bool> IdentifierActive(string group, bool active);
Task<Dictionary<string,string>> Pair(PairRequest request);
Task<AppHandshakeResponse> Handshake(AppHandshake request);
Task<bool> BroadcastTransaction(string tx);
Task<decimal> GetFeeRate(int blockTarget);
Task<BestBlockResponse> GetBestBlock();
Task<string> GetBlockHeader(string hash);
Task<TxInfoResponse> FetchTxsAndTheirBlockHeads(string[] txIds);
Task<string> DeriveScript(string identifier);
Task TrackScripts(string identifier, string[] scripts);
Task<string> UpdatePsbt(string[] identifiers, string psbt);
Task<CoinResponse[]> GetUTXOs(string[] identifiers);
Task<Dictionary<string, TxResp[]>> GetTransactions(string[] identifiers);
Task SendPaymentUpdate(string identifier, LightningPayment lightningPayment);
}
public interface IServerEvent
{
public string Type { get; }
@ -63,27 +73,13 @@ public record TxResp(long Confirmations, long? Height, decimal BalanceChange, Da
}
}
//methods available on the hub in the server
public interface IBTCPayAppHubServer
public class TransactionDetectedRequest
{
Task<bool> IdentifierActive(string group, bool active);
Task<Dictionary<string,string>> Pair(PairRequest request);
Task<AppHandshakeResponse> Handshake(AppHandshake request);
Task<bool> BroadcastTransaction(string tx);
Task<decimal> GetFeeRate(int blockTarget);
Task<BestBlockResponse> GetBestBlock();
Task<string> GetBlockHeader(string hash);
Task<TxInfoResponse> FetchTxsAndTheirBlockHeads(string[] txIds);
Task<string> DeriveScript(string identifier);
Task TrackScripts(string identifier, string[] scripts);
Task<string> UpdatePsbt(string[] identifiers, string psbt);
Task<CoinResponse[]> GetUTXOs(string[] identifiers);
Task<Dictionary<string, TxResp[]>> GetTransactions(string[] identifiers);
Task SendPaymentUpdate(string identifier, LightningPayment lightningPayment);
public string Identifier { get; set; }
public string TxId { get; set; }
public string[] SpentScripts { get; set; }
public string[] ReceivedScripts { get; set; }
public bool Confirmed { get; set; }
}
public class CoinResponse
@ -108,15 +104,12 @@ public class TransactionResponse
public string? BlockHash { get; set; }
public int? BlockHeight { get; set; }
public string Transaction { get; set; }
}
public class BestBlockResponse
{
public required string BlockHash { get; set; }
public required int BlockHeight { get; set; }
public string BlockHeader { get; set; }
}

View file

@ -101,8 +101,6 @@ public class BTCPayAppHub : Hub<IBTCPayAppHubClient>, IBTCPayAppHubServer
private readonly ILogger<BTCPayAppHub> _logger;
private readonly UserManager<ApplicationUser> _userManager;
private readonly StoreRepository _storeRepository;
private readonly EventAggregator _eventAggregator;
private CompositeDisposable? _compositeDisposable;
public BTCPayAppHub(BTCPayNetworkProvider btcPayNetworkProvider,
NBXplorerDashboard nbXplorerDashboard,
@ -111,7 +109,6 @@ public class BTCPayAppHub : Hub<IBTCPayAppHubClient>, IBTCPayAppHubServer
IFeeProviderFactory feeProviderFactory,
ILogger<BTCPayAppHub> logger,
StoreRepository storeRepository,
EventAggregator eventAggregator,
UserManager<ApplicationUser> userManager)
{
_btcPayNetworkProvider = btcPayNetworkProvider;
@ -122,7 +119,6 @@ public class BTCPayAppHub : Hub<IBTCPayAppHubClient>, IBTCPayAppHubServer
_logger = logger;
_userManager = userManager;
_storeRepository = storeRepository;
_eventAggregator = eventAggregator;
}
public override async Task OnConnectedAsync()
@ -144,31 +140,14 @@ public class BTCPayAppHub : Hub<IBTCPayAppHubClient>, IBTCPayAppHubServer
{
await Groups.AddToGroupAsync(Context.ConnectionId, userStore.Id);
}
_compositeDisposable = new CompositeDisposable();
_compositeDisposable.Add(_eventAggregator.SubscribeAsync<UserStoreAddedEvent>(StoreUserAddedEvent));
_compositeDisposable.Add(_eventAggregator.SubscribeAsync<UserStoreRemovedEvent>(StoreUserRemovedEvent));
}
public override async Task OnDisconnectedAsync(Exception? exception)
{
_compositeDisposable?.Dispose();
await _appState.Disconnected(Context.ConnectionId);
await base.OnDisconnectedAsync(exception);
}
private async Task StoreUserAddedEvent(UserStoreAddedEvent arg)
{
// TODO: Groups and COntext are not accessible here
// await Groups.AddToGroupAsync(Context.ConnectionId, arg.StoreId);
}
private async Task StoreUserRemovedEvent(UserStoreRemovedEvent arg)
{
// TODO: Groups and COntext are not accessible here
// await Groups.RemoveFromGroupAsync(Context.ConnectionId, arg.UserId);
}
public async Task<bool> BroadcastTransaction(string tx)
{
var explorerClient = _explorerClientProvider.GetExplorerClient( _btcPayNetworkProvider.BTC);
@ -198,8 +177,8 @@ public class BTCPayAppHub : Hub<IBTCPayAppHubClient>, IBTCPayAppHubServer
var explorerClient = _explorerClientProvider.GetExplorerClient( _btcPayNetworkProvider.BTC);
var bcInfo = await explorerClient.RPCClient.GetBlockchainInfoAsyncEx();
var bh = await GetBlockHeader(bcInfo.BestBlockHash.ToString());
_logger.LogInformation($"Getting best block done");
return new BestBlockResponse()
_logger.LogInformation("Getting best block done");
return new BestBlockResponse
{
BlockHash = bcInfo.BestBlockHash.ToString(),
BlockHeight = bcInfo.Blocks,
@ -209,7 +188,6 @@ public class BTCPayAppHub : Hub<IBTCPayAppHubClient>, IBTCPayAppHubServer
public async Task<string> GetBlockHeader(string hash)
{
var explorerClient = _explorerClientProvider.GetExplorerClient( _btcPayNetworkProvider.BTC);
var bh = await explorerClient.RPCClient.GetBlockHeaderAsync(uint256.Parse(hash));
return Convert.ToHexString(bh.ToBytes()).ToLower();
@ -217,12 +195,10 @@ public class BTCPayAppHub : Hub<IBTCPayAppHubClient>, IBTCPayAppHubServer
public async Task<TxInfoResponse> FetchTxsAndTheirBlockHeads(string[] txIds)
{
var cancellationToken = Context.ConnectionAborted;
var explorerClient = _explorerClientProvider.GetExplorerClient( _btcPayNetworkProvider.BTC);
var uints = txIds.Select(uint256.Parse).ToArray();
var txsFetch = await Task.WhenAll(uints.Select(
uint256 =>
var txsFetch = await Task.WhenAll(uints.Select(uint256 =>
explorerClient.GetTransactionAsync(uint256, cancellationToken)));
var batch = explorerClient.RPCClient.PrepareBatch();
@ -231,14 +207,12 @@ public class BTCPayAppHub : Hub<IBTCPayAppHubClient>, IBTCPayAppHubServer
batch.GetBlockHeaderAsync(result.BlockId, cancellationToken));
await batch.SendBatchAsync(cancellationToken);
var headerToHeight = (await Task.WhenAll(headersTask.Values)).ToDictionary(header => header.GetHash(),
header => txsFetch.First(result => result.BlockId == header.GetHash()).Height!);
return new TxInfoResponse()
return new TxInfoResponse
{
Txs = txsFetch.ToDictionary(tx => tx.TransactionHash.ToString(), tx => new TransactionResponse()
Txs = txsFetch.ToDictionary(tx => tx.TransactionHash.ToString(), tx => new TransactionResponse
{
BlockHash = tx.BlockId?.ToString(),
BlockHeight = (int?) tx.Height,
@ -248,6 +222,7 @@ public class BTCPayAppHub : Hub<IBTCPayAppHubClient>, IBTCPayAppHubServer
BlockHeghts = headerToHeight.ToDictionary(kv => kv.Key.ToString(), kv =>(int) kv.Value!)
};
}
public async Task<string> DeriveScript(string identifier)
{
var cancellationToken = Context.ConnectionAborted;
@ -353,7 +328,6 @@ var resultPsbt = PSBT.Parse(psbt, explorerClient.Network.NBitcoinNetwork);
public async Task<AppHandshakeResponse> Handshake(AppHandshake request)
{
return await _appState.Handshake(Context.ConnectionId, request);
}
}

View file

@ -88,12 +88,12 @@ public class BTCPayAppState : IHostedService
private async Task StoreCreatedEvent(StoreCreatedEvent arg)
{
await _hubContext.Clients.Group(arg.Store.Id).NotifyServerEvent(new ServerEvent<StoreCreatedEvent>("store-created", arg));
await _hubContext.Clients.Group(arg.StoreId).NotifyServerEvent(new ServerEvent<StoreCreatedEvent>("store-created", arg));
}
private async Task StoreUpdatedEvent(StoreUpdatedEvent arg)
{
await _hubContext.Clients.Group(arg.Store.Id).NotifyServerEvent(new ServerEvent<StoreUpdatedEvent>("store-updated", arg));
await _hubContext.Clients.Group(arg.StoreId).NotifyServerEvent(new ServerEvent<StoreUpdatedEvent>("store-updated", arg));
}
private async Task StoreRemovedEvent(StoreRemovedEvent arg)
@ -106,6 +106,7 @@ public class BTCPayAppState : IHostedService
var ev = new ServerEvent<UserStoreAddedEvent>("user-store-added", arg);
await _hubContext.Clients.Group(arg.StoreId).NotifyServerEvent(ev);
await _hubContext.Clients.Group(arg.UserId).NotifyServerEvent(ev);
//await _hubContext.Clients.User(arg.UserId) .AddToGroupAsync(Context.ConnectionId, arg.StoreId);
}
private async Task StoreUserUpdatedEvent(UserStoreUpdatedEvent arg)
@ -120,6 +121,7 @@ public class BTCPayAppState : IHostedService
var ev = new ServerEvent<UserStoreRemovedEvent>("user-store-removed", arg);
await _hubContext.Clients.Group(arg.StoreId).NotifyServerEvent(ev);
await _hubContext.Clients.Group(arg.UserId).NotifyServerEvent(ev);
// await Groups.RemoveFromGroupAsync(Context.ConnectionId, arg.UserId);
}
private string _nodeInfo = string.Empty;
@ -145,14 +147,11 @@ public class BTCPayAppState : IHostedService
_nodeInfo = newInf.ToString();
await _hubContext.Clients.All.NotifyServerNode(_nodeInfo);
}
}
}
catch (Exception e)
{
_logger.LogError(e, "Error during node info update");
}
await Task.Delay(TimeSpan.FromMinutes(string.IsNullOrEmpty(_nodeInfo)? 1:5), _cts.Token);
}
@ -167,10 +166,9 @@ public class BTCPayAppState : IHostedService
var explorer = _explorerClientProvider.GetExplorerClient(obj.CryptoCode);
var expandedTx = await explorer.GetTransactionAsync(obj.NewTransactionEvent.TrackedSource,
obj.NewTransactionEvent.TransactionData.TransactionHash);
await _hubContext.Clients
.Group(identifier)
.TransactionDetected(new TransactionDetectedRequest()
.TransactionDetected(new TransactionDetectedRequest
{
SpentScripts = expandedTx.Inputs.Select(input => input.ScriptPubKey.ToHex()).ToArray(),
ReceivedScripts = expandedTx.Outputs.Select(output => output.ScriptPubKey.ToHex()).ToArray(),
@ -258,7 +256,7 @@ public class BTCPayAppState : IHostedService
}
return false;
}
else if (GroupToConnectionId.TryGetValue(group, out var connId) && connId == contextConnectionId)
if (GroupToConnectionId.TryGetValue(group, out var connId) && connId == contextConnectionId)
{
return GroupToConnectionId.TryRemove(group, out _);
}
@ -290,6 +288,4 @@ public class BTCPayAppState : IHostedService
_logger.LogInformation($"Payment update for {identifier} {lightningPayment.Value} {lightningPayment.PaymentHash}");
OnPaymentUpdate?.Invoke(this, (identifier, lightningPayment));
}
}

View file

@ -2,12 +2,10 @@ using BTCPayServer.Data;
namespace BTCPayServer.Events;
public class StoreCreatedEvent(StoreData store)
public class StoreCreatedEvent(StoreData store) : StoreEvent(store)
{
public StoreData Store { get; } = store;
public override string ToString()
protected override string ToString()
{
return $"Store \"{Store.StoreName}\" has been created";
return $"{base.ToString()} has been created";
}
}

View file

@ -0,0 +1,13 @@
using BTCPayServer.Data;
namespace BTCPayServer.Events;
public class StoreEvent(StoreData store)
{
public string StoreId { get; } = store.Id;
protected new virtual string ToString()
{
return $"StoreEvent: Store \"{store.StoreName}\" ({store.Id})";
}
}

View file

@ -1,11 +1,11 @@
using BTCPayServer.Data;
namespace BTCPayServer.Events;
public class StoreRemovedEvent(string storeId)
public class StoreRemovedEvent(StoreData store) : StoreEvent(store)
{
public string StoreId { get; } = storeId;
public override string ToString()
protected override string ToString()
{
return $"Store {StoreId} has been removed";
return $"{base.ToString()} has been removed";
}
}

View file

@ -2,12 +2,10 @@ using BTCPayServer.Data;
namespace BTCPayServer.Events;
public class StoreUpdatedEvent(StoreData store)
public class StoreUpdatedEvent(StoreData store) : StoreEvent(store)
{
public StoreData Store { get; } = store;
public override string ToString()
protected override string ToString()
{
return $"Store \"{Store.StoreName}\" has been updated";
return $"{base.ToString()} has been updated";
}
}

View file

@ -2,8 +2,8 @@ namespace BTCPayServer.Events;
public class UserStoreAddedEvent(string storeId, string userId) : UserStoreEvent(storeId, userId)
{
public override string ToString()
protected override string ToString()
{
return $"User {UserId} has been added to store {StoreId}";
return $"{base.ToString()} has been added";
}
}

View file

@ -4,7 +4,8 @@ public abstract class UserStoreEvent(string storeId, string userId)
{
public string StoreId { get; } = storeId;
public string UserId { get; } = userId;
public new virtual string ToString()
protected new virtual string ToString()
{
return $"StoreUserEvent: User {UserId}, Store {StoreId}";
}

View file

@ -2,8 +2,8 @@ namespace BTCPayServer.Events;
public class UserStoreRemovedEvent(string storeId, string userId) : UserStoreEvent(storeId, userId)
{
public override string ToString()
protected override string ToString()
{
return $"User {UserId} has been removed from store {StoreId}";
return $"{base.ToString()} has been removed";
}
}

View file

@ -2,8 +2,8 @@ namespace BTCPayServer.Events;
public class UserStoreUpdatedEvent(string storeId, string userId) : UserStoreEvent(storeId, userId)
{
public override string ToString()
protected override string ToString()
{
return $"User {UserId} and store {StoreId} relation has been updated";
return $"{base.ToString()} has been updated";
}
}

View file

@ -347,14 +347,14 @@ namespace BTCPayServer.Services.Stores
public async Task CleanUnreachableStores()
{
await using var ctx = _ContextFactory.CreateContext();
var events = new List<Events.StoreRemovedEvent>();
var events = new List<StoreRemovedEvent>();
foreach (var store in await ctx.Stores.Include(data => data.UserStores)
.ThenInclude(store => store.StoreRole).Where(s =>
s.UserStores.All(u => !u.StoreRole.Permissions.Contains(Policies.CanModifyStoreSettings)))
.ToArrayAsync())
{
ctx.Stores.Remove(store);
events.Add(new Events.StoreRemovedEvent(store.Id));
events.Add(new StoreRemovedEvent(store));
}
await ctx.SaveChangesAsync();
events.ForEach(e => _eventAggregator.Publish(e));
@ -385,7 +385,7 @@ namespace BTCPayServer.Services.Stores
{
ctx.Stores.Remove(store);
await ctx.SaveChangesAsync();
_eventAggregator.Publish(new StoreRemovedEvent(store.Id));
_eventAggregator.Publish(new StoreRemovedEvent(store));
}
}
}
@ -583,7 +583,7 @@ retry:
try
{
await ctx.SaveChangesAsync();
_eventAggregator.Publish(new StoreRemovedEvent(store.Id));
_eventAggregator.Publish(new StoreRemovedEvent(store));
}
catch (DbUpdateException ex) when (IsDeadlock(ex) && retry < 5)
{