protected override async Task ExecuteAsync(CancellationToken stoppingToken) { if (WalletManager.Network == Network.Main) { Logger.LogInfo("WabiSabi coinjoin client-side functionality is disabled temporarily on mainnet."); return; } var trackedWallets = new Dictionary <string, WalletTrackingData>(); while (!stoppingToken.IsCancellationRequested) { await Task.Delay(TimeSpan.FromSeconds(1), stoppingToken).ConfigureAwait(false); var mixableWallets = GetMixableWallets(); var openedWallets = mixableWallets.Where(x => !trackedWallets.ContainsKey(x.Key)); var closedWallets = trackedWallets.Where(x => !mixableWallets.ContainsKey(x.Key)); foreach (var openedWallet in openedWallets.Select(x => x.Value)) { var coinjoinClient = new CoinJoinClient(ArenaRequestHandler, openedWallet.Kitchen, openedWallet.KeyManager, RoundStatusUpdater); var cts = CancellationTokenSource.CreateLinkedTokenSource(stoppingToken); var coinCandidates = openedWallet.Coins.Available().Confirmed().Where(x => x.HdPubKey.AnonymitySet < ServiceConfiguration.GetMixUntilAnonymitySetValue()); var coinjoinTask = coinjoinClient.StartCoinJoinAsync(coinCandidates, cts.Token); trackedWallets.Add(openedWallet.WalletName, new WalletTrackingData(openedWallet, coinjoinClient, coinjoinTask, cts)); } foreach (var closedWallet in closedWallets.Select(x => x.Value)) { closedWallet.CancellationTokenSource.Cancel(); closedWallet.CancellationTokenSource.Dispose(); } var finishedCoinJoins = trackedWallets .Where(x => x.Value.CoinJoinTask.IsCompleted) .Select(x => x.Value) .ToImmutableArray(); foreach (var finishedCoinJoin in finishedCoinJoins) { var removed = trackedWallets.Remove(finishedCoinJoin.Wallet.WalletName); if (!removed) { Logger.LogWarning($"Wallet: `{finishedCoinJoin.Wallet.WalletName}` was not removed from tracked wallet list. Will retry in a few seconds."); } finishedCoinJoin.CancellationTokenSource.Dispose(); } foreach (var finishedCoinJoin in finishedCoinJoins) { var logPrefix = $"Wallet: `{finishedCoinJoin.Wallet.WalletName}` - Coinjoin client"; try { var success = await finishedCoinJoin.CoinJoinTask.ConfigureAwait(false); if (success) { Logger.LogInfo($"{logPrefix} finished successfully!"); } else { Logger.LogInfo($"{logPrefix} finished with error. Transaction not broadcasted."); } } catch (InvalidOperationException ioe) { Logger.LogError(ioe); await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken).ConfigureAwait(false); } catch (OperationCanceledException) { Logger.LogInfo($"{logPrefix} was cancelled."); } catch (Exception e) { Logger.LogError($"{logPrefix} failed with exception:", e); } } TrackedWallets = trackedWallets.ToImmutableDictionary(); } }
private record WalletTrackingData(Wallet Wallet, CoinJoinClient CoinJoinClient, Task <bool> CoinJoinTask, CancellationTokenSource CancellationTokenSource);