コード例 #1
0
 public bool ContainsCoinJoin(uint256 hash)
 {
     using (CoinJoinsLock.Lock())
     {
         return(CoinJoins.Contains(hash));
     }
 }
コード例 #2
0
        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);
            }
        }
コード例 #3
0
        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();
            }
        }
コード例 #4
0
 public async Task <bool> ContainsCoinJoinAsync(uint256 hash)
 {
     using (await CoinJoinsLock.LockAsync())
     {
         return(CoinJoins.Contains(hash));
     }
 }
コード例 #5
0
        /// <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);
            }
        }
コード例 #6
0
ファイル: CcjCoordinator.cs プロジェクト: knocte/WalletWasabi
        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);
            }
        }
コード例 #7
0
        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);
            }
        }
コード例 #8
0
ファイル: CcjCoordinator.cs プロジェクト: rude9/WalletWasabi
        /// <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);
            }
        }
コード例 #9
0
        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();
                                }
                            }
                        }
                    }
コード例 #10
0
ファイル: CcjCoordinator.cs プロジェクト: rude9/WalletWasabi
        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);
            }
        }
コード例 #11
0
ファイル: Coordinator.cs プロジェクト: yahiheb/WalletWasabi
        private volatile bool _disposedValue = false;         // To detect redundant calls

        public Coordinator(Network network, BlockNotifier blockNotifier, string folderPath, IRPCClient rpc, CoordinatorRoundConfig roundConfig)
        {
            Network       = Guard.NotNull(nameof(network), network);
            BlockNotifier = Guard.NotNull(nameof(blockNotifier), blockNotifier);
            FolderPath    = Guard.NotNullOrEmptyOrWhitespace(nameof(folderPath), folderPath, trim: true);
            RpcClient     = Guard.NotNull(nameof(rpc), rpc);
            RoundConfig   = Guard.NotNull(nameof(roundConfig), roundConfig);

            Rounds         = new List <CoordinatorRound>();
            RoundsListLock = new AsyncLock();

            LastSuccessfulCoinJoinTime = DateTimeOffset.UtcNow;

            Directory.CreateDirectory(FolderPath);

            UtxoReferee = new UtxoReferee(Network, FolderPath, RpcClient, RoundConfig);

            if (File.Exists(CoinJoinsFilePath))
            {
                try
                {
                    var getTxTasks = new List <(Task <Transaction> txTask, string line)>();
                    var batch      = RpcClient.PrepareBatch();

                    var      toRemove = new List <string>();
                    string[] allLines = File.ReadAllLines(CoinJoinsFilePath);
                    foreach (string line in allLines)
                    {
                        try
                        {
                            getTxTasks.Add((batch.GetRawTransactionAsync(uint256.Parse(line)), line));
                        }
                        catch (Exception ex)
                        {
                            toRemove.Add(line);

                            var logEntry = ex is RPCException rpce && rpce.RPCCode == RPCErrorCode.RPC_INVALID_ADDRESS_OR_KEY
                                                                ? $"CoinJoins file contains invalid transaction ID {line}"
                                                                : $"CoinJoins file got corrupted. Deleting offending line \"{line.Substring(0, 20)}\".";

                            Logger.LogWarning($"{logEntry}. {ex.GetType()}: {ex.Message}");
                        }
                    }

                    batch.SendBatchAsync().GetAwaiter().GetResult();

                    foreach (var(txTask, line) in getTxTasks)
                    {
                        try
                        {
                            var tx = txTask.GetAwaiter().GetResult();
                            CoinJoins.Add(tx.GetHash());
                        }
                        catch (Exception ex)
                        {
                            toRemove.Add(line);

                            var logEntry = ex is RPCException rpce && rpce.RPCCode == RPCErrorCode.RPC_INVALID_ADDRESS_OR_KEY
                                                                ? $"CoinJoins file contains invalid transaction ID {line}"
                                                                : $"CoinJoins file got corrupted. Deleting offending line \"{line.Substring(0, 20)}\".";

                            Logger.LogWarning($"{logEntry}. {ex.GetType()}: {ex.Message}");
                        }
                    }

                    if (toRemove.Count != 0)                     // a little performance boost, it'll be empty almost always
                    {
                        var newAllLines = allLines.Where(x => !toRemove.Contains(x));
                        File.WriteAllLines(CoinJoinsFilePath, newAllLines);
                    }
                }
                catch (Exception ex)
                {
                    Logger.LogWarning($"CoinJoins file got corrupted. Deleting {CoinJoinsFilePath}. {ex.GetType()}: {ex.Message}");
                    File.Delete(CoinJoinsFilePath);
                }

                uint256[] mempoolHashes = RpcClient.GetRawMempoolAsync().GetAwaiter().GetResult();
                UnconfirmedCoinJoins.AddRange(CoinJoins.Intersect(mempoolHashes));
            }

            try
            {
                string roundCountFilePath = Path.Combine(folderPath, "RoundCount.txt");
                if (File.Exists(roundCountFilePath))
                {
                    string roundCount = File.ReadAllText(roundCountFilePath);
                    CoordinatorRound.RoundCount = long.Parse(roundCount);
                }
                else
                {
                    // First time initializes (so the first constructor will increment it and we'll start from 1.)
                    CoordinatorRound.RoundCount = 0;
                }
            }
            catch (Exception ex)
            {
                CoordinatorRound.RoundCount = 0;
                Logger.LogInfo($"{nameof(CoordinatorRound.RoundCount)} file was corrupt. Resetting to 0.");
                Logger.LogDebug(ex);
            }

            BlockNotifier.OnBlock += BlockNotifier_OnBlockAsync;
        }