private async void Round_StatusChangedAsync(object sender, CcjRoundStatus status) { var round = sender as CcjRound; Money feePerInputs = null; Money feePerOutputs = null; // If success save the coinjoin. if (status == CcjRoundStatus.Succeded) { using (await CoinJoinsLock.LockAsync()) { uint256 coinJoinHash = round.SignedCoinJoin.GetHash(); CoinJoins.Add(coinJoinHash); await File.AppendAllLinesAsync(CoinJoinsFilePath, new[] { coinJoinHash.ToString() }); // 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.SignedCoinJoin.GetIndistinguishableOutputs(); var bestOutput = outputs.OrderByDescending(x => x.count).FirstOrDefault(); if (bestOutput != default) { Money activeOutputAmount = bestOutput.value; var fees = await CcjRound.CalculateFeesAsync(RpcClient, RoundConfig.ConfirmationTarget.Value); 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; await RoundConfig.ToFileAsync(); } } } } } // If aborted in signing phase, then ban Alices those didn't sign. if (status == CcjRoundStatus.Aborted && round.Phase == CcjRoundPhase.Signing) { foreach (Alice alice in round.GetAlicesByNot(AliceState.SignedCoinJoin, syncLock: false)) // Because the event sometimes is raised from inside the lock. { // If its from any coinjoin, then don't ban. IEnumerable <OutPoint> utxosToBan = alice.Inputs.Select(x => x.Outpoint); await UtxoReferee.BanUtxosAsync(1, DateTimeOffset.UtcNow, forceNoted : false, round.RoundId, utxosToBan.ToArray()); } } // If finished start a new round. if (status == CcjRoundStatus.Aborted || status == CcjRoundStatus.Succeded) { round.StatusChanged -= Round_StatusChangedAsync; round.CoinJoinBroadcasted -= Round_CoinJoinBroadcasted; await MakeSureTwoRunningRoundsAsync(feePerInputs, feePerOutputs); } }
private async void Round_StatusChangedAsync(object sender, CcjRoundStatus status) { try { var round = sender as CcjRound; Money feePerInputs = null; Money feePerOutputs = null; // If success save the coinjoin. if (status == CcjRoundStatus.Succeded) { using (await CoinJoinsLock.LockAsync()) { uint256 coinJoinHash = round.SignedCoinJoin.GetHash(); CoinJoins.Add(coinJoinHash); await File.AppendAllLinesAsync(CoinJoinsFilePath, new[] { coinJoinHash.ToString() }); // 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.SignedCoinJoin.GetIndistinguishableOutputs(includeSingle: true); var bestOutput = outputs.OrderByDescending(x => x.count).FirstOrDefault(); if (bestOutput != default) { Money activeOutputAmount = bestOutput.value; int currentConfirmationTarget = await AdjustConfirmationTargetAsync(lockCoinJoins : false); var fees = await CcjRound.CalculateFeesAsync(RpcClient, currentConfirmationTarget); 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; await RoundConfig.ToFileAsync(); } } } } } // If aborted in signing phase, then ban Alices those didn't sign. if (status == CcjRoundStatus.Aborted && round.Phase == CcjRoundPhase.Signing) { IEnumerable <Alice> alicesDidntSign = round.GetAlicesByNot(AliceState.SignedCoinJoin, syncLock: false); CcjRound 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 those 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 doesnt equal and newanonset 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(CcjRoundPhase.ConnectionConfirmation); } } } foreach (Alice alice in alicesDidntSign) // Because the event sometimes is raised from inside the lock. { // If its from any coinjoin, then don't ban. IEnumerable <OutPoint> utxosToBan = alice.Inputs.Select(x => x.Outpoint); await UtxoReferee.BanUtxosAsync(1, DateTimeOffset.UtcNow, forceNoted : false, round.RoundId, utxosToBan.ToArray()); } } // If finished start a new round. if (status == CcjRoundStatus.Aborted || status == CcjRoundStatus.Succeded) { round.StatusChanged -= Round_StatusChangedAsync; round.CoinJoinBroadcasted -= Round_CoinJoinBroadcasted; await MakeSureTwoRunningRoundsAsync(feePerInputs, feePerOutputs); } } catch (Exception ex) { Logger.LogWarning <CcjCoordinator>(ex); } }