Esempio n. 1
0
        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);
            }
        }
        public async Task <IActionResult> PostConfirmationAsync([FromQuery] string uniqueId, [FromQuery] long roundId)
        {
            if (roundId <= 0 || !ModelState.IsValid)
            {
                return(BadRequest());
            }

            Guid uniqueIdGuid = CheckUniqueId(uniqueId, out IActionResult returnFailureResponse);

            if (returnFailureResponse != null)
            {
                return(returnFailureResponse);
            }

            CcjRound round = Coordinator.TryGetRound(roundId);

            if (round == null)
            {
                return(NotFound("Round not found."));
            }

            Alice alice = round.TryGetAliceBy(uniqueIdGuid);

            if (round == null)
            {
                return(NotFound("Alice not found."));
            }

            if (round.Status != CcjRoundStatus.Running)
            {
                return(Forbid("Round is not running."));
            }

            CcjRoundPhase phase = round.Phase;

            switch (phase)
            {
            case CcjRoundPhase.InputRegistration:
            {
                round.StartAliceTimeout(uniqueIdGuid);
                return(NoContent());
            }

            case CcjRoundPhase.ConnectionConfirmation:
            {
                alice.State = AliceState.ConnectionConfirmed;

                // Progress round if needed.
                if (round.AllAlices(AliceState.ConnectionConfirmed))
                {
                    IEnumerable <Alice> alicesToBan = await round.RemoveAlicesIfInputsSpentAsync();                                    // So ban only those who confirmed participation, yet spent their inputs.

                    if (alicesToBan.Count() > 0)
                    {
                        await Coordinator.UtxoReferee.BanUtxosAsync(1, DateTimeOffset.Now, alicesToBan.SelectMany(x => x.Inputs).Select(y => y.OutPoint).ToArray());
                    }

                    int aliceCountAfterConnectionConfirmationTimeout = round.CountAlices();
                    if (aliceCountAfterConnectionConfirmationTimeout < 2)
                    {
                        round.Fail();
                    }
                    else
                    {
                        round.UpdateAnonymitySet(aliceCountAfterConnectionConfirmationTimeout);
                        // Progress to the next phase, which will be OutputRegistration
                        await round.ExecuteNextPhaseAsync(CcjRoundPhase.OutputRegistration);
                    }
                }

                return(Ok(round.RoundHash));                                // Participation can be confirmed multiple times, whatever.
            }

            default:
            {
                return(Forbid($"Participation can be only confirmed from InputRegistration or ConnectionConfirmation phase. Current phase: {phase}."));
            }
            }
        }