private async void Round_StatusChangedAsync(object sender, CoordinatorRoundStatus status) { try { var round = sender as CoordinatorRound; Money feePerInputs = null; Money feePerOutputs = null; // If success save the coinjoin. if (status == CoordinatorRoundStatus.Succeded) { uint256[] mempoolHashes = null; try { mempoolHashes = await RpcClient.GetRawMempoolAsync().ConfigureAwait(false); } catch (Exception ex) { Logger.LogError(ex); } using (await CoinJoinsLock.LockAsync().ConfigureAwait(false)) { if (mempoolHashes is { }) { var fallOuts = UnconfirmedCoinJoins.Where(x => !mempoolHashes.Contains(x)); CoinJoins.RemoveAll(x => fallOuts.Contains(x)); UnconfirmedCoinJoins.RemoveAll(x => fallOuts.Contains(x)); } uint256 coinJoinHash = round.CoinJoin.GetHash(); CoinJoins.Add(coinJoinHash); UnconfirmedCoinJoins.Add(coinJoinHash); LastSuccessfulCoinJoinTime = DateTimeOffset.UtcNow; await File.AppendAllLinesAsync(CoinJoinsFilePath, new[] { coinJoinHash.ToString() }).ConfigureAwait(false); // When a round succeeded, adjust the denomination as to users still be able to register with the latest round's active output amount. IEnumerable <(Money value, int count)> outputs = round.CoinJoin.GetIndistinguishableOutputs(includeSingle: true); var bestOutput = outputs.OrderByDescending(x => x.count).FirstOrDefault(); if (bestOutput != default) { Money activeOutputAmount = bestOutput.value; int currentConfirmationTarget = await AdjustConfirmationTargetAsync(lockCoinJoins : false).ConfigureAwait(false); var fees = await CoordinatorRound.CalculateFeesAsync(RpcClient, currentConfirmationTarget).ConfigureAwait(false); feePerInputs = fees.feePerInputs; feePerOutputs = fees.feePerOutputs; Money newDenominationToGetInWithactiveOutputs = activeOutputAmount - (feePerInputs + (2 * feePerOutputs)); if (newDenominationToGetInWithactiveOutputs < RoundConfig.Denomination) { if (newDenominationToGetInWithactiveOutputs > Money.Coins(0.01m)) { RoundConfig.Denomination = newDenominationToGetInWithactiveOutputs; RoundConfig.ToFile(); } } } }
private async void Round_StatusChangedAsync(object sender, CoordinatorRoundStatus status) { try { var round = sender as CoordinatorRound; Money feePerInputs = null; Money feePerOutputs = null; // If success save the coinjoin. if (status == CoordinatorRoundStatus.Succeded) { using (await CoinJoinsLock.LockAsync().ConfigureAwait(false)) { uint256 coinJoinHash = round.CoinJoin.GetHash(); CoinJoins.Add(coinJoinHash); UnconfirmedCoinJoins.Add(coinJoinHash); LastSuccessfulCoinJoinTime = DateTimeOffset.UtcNow; await File.AppendAllLinesAsync(CoinJoinsFilePath, new[] { coinJoinHash.ToString() }).ConfigureAwait(false); // When a round succeeded, adjust the denomination as to users still be able to register with the latest round's active output amount. IEnumerable <(Money value, int count)> outputs = round.CoinJoin.GetIndistinguishableOutputs(includeSingle: true); var bestOutput = outputs.OrderByDescending(x => x.count).FirstOrDefault(); if (bestOutput != default) { Money activeOutputAmount = bestOutput.value; int currentConfirmationTarget = await AdjustConfirmationTargetAsync(lockCoinJoins : false).ConfigureAwait(false); var fees = await CoordinatorRound.CalculateFeesAsync(RpcClient, currentConfirmationTarget).ConfigureAwait(false); feePerInputs = fees.feePerInputs; feePerOutputs = fees.feePerOutputs; Money newDenominationToGetInWithactiveOutputs = activeOutputAmount - (feePerInputs + (2 * feePerOutputs)); if (newDenominationToGetInWithactiveOutputs < RoundConfig.Denomination) { if (newDenominationToGetInWithactiveOutputs > Money.Coins(0.01m)) { RoundConfig.Denomination = newDenominationToGetInWithactiveOutputs; RoundConfig.ToFile(); } } } } } // If aborted in signing phase, then ban Alices that did not sign. if (status == CoordinatorRoundStatus.Aborted && round.Phase == RoundPhase.Signing) { IEnumerable <Alice> alicesDidntSign = round.GetAlicesByNot(AliceState.SignedCoinJoin, syncLock: false); CoordinatorRound nextRound = GetCurrentInputRegisterableRoundOrDefault(syncLock: false); if (nextRound != null) { int nextRoundAlicesCount = nextRound.CountAlices(syncLock: false); var alicesSignedCount = round.AnonymitySet - alicesDidntSign.Count(); // New round's anonset should be the number of alices that signed in this round. // Except if the number of alices in the next round is already larger. var newAnonymitySet = Math.Max(alicesSignedCount, nextRoundAlicesCount); // But it cannot be larger than the current anonset of that round. newAnonymitySet = Math.Min(newAnonymitySet, nextRound.AnonymitySet); // Only change the anonymity set of the next round if new anonset does not equal and newanonset is larger than 1. if (nextRound.AnonymitySet != newAnonymitySet && newAnonymitySet > 1) { nextRound.UpdateAnonymitySet(newAnonymitySet, syncLock: false); if (nextRoundAlicesCount >= nextRound.AnonymitySet) { // Progress to the next phase, which will be OutputRegistration await nextRound.ExecuteNextPhaseAsync(RoundPhase.ConnectionConfirmation).ConfigureAwait(false); } } } foreach (Alice alice in alicesDidntSign) // Because the event sometimes is raised from inside the lock. { // If it is from any coinjoin, then do not ban. IEnumerable <OutPoint> utxosToBan = alice.Inputs.Select(x => x.Outpoint); await UtxoReferee.BanUtxosAsync(1, DateTimeOffset.UtcNow, forceNoted : false, round.RoundId, utxosToBan.ToArray()).ConfigureAwait(false); } } // If finished start a new round. if (status == CoordinatorRoundStatus.Aborted || status == CoordinatorRoundStatus.Succeded) { round.StatusChanged -= Round_StatusChangedAsync; round.CoinJoinBroadcasted -= Round_CoinJoinBroadcasted; await MakeSureTwoRunningRoundsAsync(feePerInputs, feePerOutputs).ConfigureAwait(false); } } catch (Exception ex) { Logger.LogWarning(ex); } }