/// <summary> /// Depending on the number of unconfirmed coinjoins lower the confirmation target. /// https://github.com/zkSNACKs/WalletWasabi/issues/1155 /// </summary> private async Task <int> AdjustConfirmationTargetAsync(bool lockCoinJoins) { try { int unconfirmedCoinJoinsCount = 0; if (lockCoinJoins) { using (await CoinJoinsLock.LockAsync().ConfigureAwait(false)) { unconfirmedCoinJoinsCount = UnconfirmedCoinJoins.Count; } } else { unconfirmedCoinJoinsCount = UnconfirmedCoinJoins.Count; } int confirmationTarget = CoordinatorRound.AdjustConfirmationTarget(unconfirmedCoinJoinsCount, RoundConfig.ConfirmationTarget, RoundConfig.ConfirmationTargetReductionRate); return(confirmationTarget); } catch (Exception ex) { Logger.LogWarning("Adjusting confirmation target failed. Falling back to default, specified in config."); Logger.LogWarning(ex); return(RoundConfig.ConfirmationTarget); } }
public async Task <IEnumerable <uint256> > GetUnconfirmedCoinJoinsAsync() { using (await CoinJoinsLock.LockAsync()) { return(UnconfirmedCoinJoins.ToArray()); } }
public bool ContainsCoinJoin(uint256 hash) { using (CoinJoinsLock.Lock()) { return(CoinJoins.Contains(hash)); } }
public async Task <bool> IsUnconfirmedCoinJoinLimitReachedAsync() { using (await CoinJoinsLock.LockAsync()) { if (UnconfirmedCoinJoins.Count() < 24) { return(false); } else { foreach (var cjHash in UnconfirmedCoinJoins) { RPCResponse getRawTransactionResponse = await RpcClient.SendCommandAsync(RPCOperations.getrawtransaction, cjHash.ToString(), true); // if failed remove from everywhere (should not happen normally) if (string.IsNullOrWhiteSpace(getRawTransactionResponse?.ResultString)) { UnconfirmedCoinJoins.Remove(cjHash); CoinJoins.Remove(cjHash); await File.WriteAllLinesAsync(CoinJoinsFilePath, CoinJoins.Select(x => x.ToString())); } // if confirmed remove only from unconfirmed if (getRawTransactionResponse.Result.Value <int>("confirmations") > 0) { UnconfirmedCoinJoins.Remove(cjHash); } } } return(UnconfirmedCoinJoins.Count() >= 24); } }
public async Task <bool> ContainsUnconfirmedCoinJoinAsync(uint256 hash) { using (await CoinJoinsLock.LockAsync().ConfigureAwait(false)) { return(UnconfirmedCoinJoins.Contains(hash)); } }
private async void Round_StatusChangedAsync(object sender, CcjRoundStatus status) { var round = sender as CcjRound; // 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() }); } } // If failed in signing phase, then ban Alices those didn't sign. if (status == CcjRoundStatus.Failed && 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, utxosToBan.ToArray()); } } // If finished start a new round. if (status == CcjRoundStatus.Failed || status == CcjRoundStatus.Succeded) { round.StatusChanged -= Round_StatusChangedAsync; await MakeSureTwoRunningRoundsAsync(); } }
public async Task <bool> IsUnconfirmedCoinJoinLimitReachedAsync() { using (await CoinJoinsLock.LockAsync()) { if (UnconfirmedCoinJoins.Count < 24) { return(false); } foreach (var cjHash in UnconfirmedCoinJoins.ToArray()) { try { var txInfo = await RpcClient.GetRawTransactionInfoAsync(cjHash); // if confirmed remove only from unconfirmed if (txInfo.Confirmations > 0) { UnconfirmedCoinJoins.Remove(cjHash); } } catch (Exception ex) { // If aborted remove from everywhere (should not happen normally). UnconfirmedCoinJoins.Remove(cjHash); CoinJoins.Remove(cjHash); await File.WriteAllLinesAsync(CoinJoinsFilePath, CoinJoins.Select(x => x.ToString())); Logger.LogWarning <CcjCoordinator>(ex); } } return(UnconfirmedCoinJoins.Count >= 24); } }
public async Task <bool> ContainsCoinJoinAsync(uint256 hash) { using (await CoinJoinsLock.LockAsync()) { return(CoinJoins.Contains(hash)); } }
/// <summary> /// Depending on the number of unconfirmed coinjoins lower the confirmation target. /// https://github.com/zkSNACKs/WalletWasabi/issues/1155 /// </summary> private async Task <int> AdjustConfirmationTargetAsync(bool lockCoinJoins) { try { uint256[] mempoolHashes = await RpcClient.GetRawMempoolAsync(); int unconfirmedCoinJoinsCount = 0; if (lockCoinJoins) { using (await CoinJoinsLock.LockAsync()) { unconfirmedCoinJoinsCount = CoinJoins.Intersect(mempoolHashes).Count(); } } else { unconfirmedCoinJoinsCount = CoinJoins.Intersect(mempoolHashes).Count(); } int confirmationTarget = CcjRound.AdjustConfirmationTarget(unconfirmedCoinJoinsCount, RoundConfig.ConfirmationTarget.Value, RoundConfig.ConfirmationTargetReductionRate.Value); return(confirmationTarget); } catch (Exception ex) { Logger.LogWarning <CcjCoordinator>("Adjusting confirmation target failed. Falling back to default, specified in config."); Logger.LogWarning <CcjCoordinator>(ex); return(RoundConfig.ConfirmationTarget.Value); } }
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 BlockNotifier_OnBlockAsync(object?sender, Block block) { try { using (await CoinJoinsLock.LockAsync()) { foreach (Transaction tx in block.Transactions) { await ProcessConfirmedTransactionAsync(tx).ConfigureAwait(false); } } } catch (Exception ex) { Logger.LogWarning(ex); } }
/// <summary> /// Depending on the number of unconfirmed coinjoins lower the confirmation target. /// https://github.com/zkSNACKs/WalletWasabi/issues/1155 /// </summary> private async Task <int> AdjustConfirmationTargetAsync(bool lockCoinJoins) { try { uint256[] mempoolHashes = await RpcClient.GetRawMempoolAsync(); int unconfirmedCoinJoinsCount = 0; if (lockCoinJoins) { using (await CoinJoinsLock.LockAsync()) { unconfirmedCoinJoinsCount = CoinJoins.Intersect(mempoolHashes).Count(); } } else { unconfirmedCoinJoinsCount = CoinJoins.Intersect(mempoolHashes).Count(); } int confirmationTarget = RoundConfig.ConfirmationTarget.Value; for (int i = 0; i < unconfirmedCoinJoinsCount; i++) { confirmationTarget /= 2; } confirmationTarget = Math.Max(confirmationTarget, 2); // Conf target should never be less than 2. return(confirmationTarget); } catch (Exception ex) { Logger.LogWarning <CcjCoordinator>("Adjusting confirmation target failed. Falling back to default, specified in config."); Logger.LogWarning <CcjCoordinator>(ex); return(RoundConfig.ConfirmationTarget.Value); } }
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, 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); } }