Esempio n. 1
0
 public IEnumerable <CcjRound> GetRunningRounds()
 {
     using (RoundsListLock.Lock())
     {
         return(Rounds.Where(x => x.Status == CcjRoundStatus.Running).ToArray());
     }
 }
Esempio n. 2
0
 public void AbortAllRoundsInInputRegistration(string reason, [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = -1)
 {
     using (RoundsListLock.Lock())
     {
         foreach (var r in Rounds.Where(x => x.Status == CoordinatorRoundStatus.Running && x.Phase == RoundPhase.InputRegistration))
         {
             r.Abort(reason, callerFilePath: callerFilePath, callerLineNumber: callerLineNumber);
         }
     }
 }
Esempio n. 3
0
 public void FailAllRoundsInInputRegistration()
 {
     using (RoundsListLock.Lock())
     {
         foreach (var r in Rounds.Where(x => x.Status == CcjRoundStatus.Running && x.Phase == CcjRoundPhase.InputRegistration))
         {
             r.Fail();
         }
     }
 }
Esempio n. 4
0
		public void AbortAllRoundsInInputRegistration(string loggingCategory, string reason)
		{
			string category = string.IsNullOrWhiteSpace(loggingCategory) ? nameof(CcjCoordinator) : loggingCategory;
			using (RoundsListLock.Lock())
			{
				foreach (var r in Rounds.Where(x => x.Status == CcjRoundStatus.Running && x.Phase == CcjRoundPhase.InputRegistration))
				{
					r.Abort(category, reason);
				}
			}
		}
Esempio n. 5
0
        private double GetAverageHealthDamage()
        {
            double total = Rounds.Where(round => round.PlayersHurted.Any()).SelectMany(
                round => round.PlayersHurted).Aggregate <PlayerHurtedEvent, double>(0, (current, playerHurtedEvent) =>
                                                                                    current + playerHurtedEvent.HealthDamage);

            if (Math.Abs(total) < 0.1)
            {
                return(total);
            }
            total = Math.Round(total / Rounds.Count, 1);
            return(total);
        }
Esempio n. 6
0
 public void AddOrReplaceRound(CcjClientRound round)
 {
     lock (StateLock)
     {
         foreach (var r in Rounds.Where(x => x.State.RoundId == round.State.RoundId))
         {
             r?.Registration?.AliceClient?.Dispose();
             Logger.LogInfo <CcjClientState>($"Round ({round.State.RoundId}) removed. Reason: It's being replaced.");
         }
         Rounds.RemoveAll(x => x.State.RoundId == round.State.RoundId);
         Rounds.Add(round);
         Logger.LogInfo <CcjClientState>($"Round ({round.State.RoundId}) added.");
     }
 }
Esempio n. 7
0
 public void ClearRoundRegistration(long roundId)
 {
     lock (StateLock)
     {
         foreach (var round in Rounds.Where(x => x.State.RoundId == roundId))
         {
             foreach (var coin in round.CoinsRegistered)
             {
                 WaitingList.Add(coin, DateTimeOffset.UtcNow);
                 Logger.LogInfo <CcjClientState>($"Coin added to the waiting list: {coin.Index}:{coin.TransactionId}.");
             }
             round.ClearRegistration();
             Logger.LogInfo <CcjClientState>($"Round ({round.State.RoundId}) registration is cleared.");
         }
     }
 }
Esempio n. 8
0
        public ITournament AdvanceRound()
        {
            Queue <ITournamentEntry> teamQueue = new Queue <ITournamentEntry>();
            var activeRound = Rounds.Where(x => x.RoundNum == ActiveRound).First();

            foreach (var pairing in activeRound.Matchups)
            {
                var winner = pairing.MatchupEntries.OrderByDescending(x => x.Score).First().TheTeam;
                teamQueue.Enqueue(winner);
            }

            ActiveRound++;
            var nextRound = Rounds.Where(x => x.RoundNum == ActiveRound).First();

            if (nextRound != null)
            {
                for (int i = 0; i < teamQueue.Count / 2; i++)
                {
                    nextRound.Matchups.Add(new Matchup()
                    {
                        MatchupId      = i,
                        MatchupEntries = new List <IMatchupEntry>()
                    });
                }

                foreach (var matchup in nextRound.Matchups)
                {
                    matchup.MatchupEntries.Add(new MatchupEntry()
                    {
                        TheTeam = teamQueue.Dequeue(),
                        Score   = 0
                    });

                    matchup.MatchupEntries.Add(new MatchupEntry()
                    {
                        TheTeam = teamQueue.Dequeue(),
                        Score   = 0
                    });

                    NotificationHelper.NotifyParticipants(matchup, this);
                }
            }

            return(this);
        }
Esempio n. 9
0
 public bool AnyRunningRoundContainsInput(OutPoint input, out List <Alice> alices)
 {
     using (RoundsListLock.Lock())
     {
         alices = new List <Alice>();
         foreach (var round in Rounds.Where(x => x.Status == CcjRoundStatus.Running))
         {
             if (round.ContainsInput(input, out List <Alice> roundAlices))
             {
                 foreach (var alice in roundAlices)
                 {
                     alices.Add(alice);
                 }
             }
         }
         return(alices.Count > 0);
     }
 }
Esempio n. 10
0
        public Score Score()
        {
            var scores = Rounds
                         .Where(r => !r.BothTeamsLostRewards)
                         .GroupBy(r => r.WinnerTeamId)
                         .Select(g =>
                                 new GameScore(
                                     g.Key,
                                     g.Count(),
                                     Players.FirstOrDefault(p => p.TeamId == g.Key).CoinsLeft()))
                         .OrderByDescending(s => s.RoundsWon).ToList();

            if (scores.Count == 1)
            {
                return(scores.Select(s => new Score {
                    WinnerTeamId = s.TeamId, Rule = WinnerRule.MoreRoundsWon
                }).FirstOrDefault());
            }

            if (scores.Select(s => s.RoundsWon).Distinct().Count() != 1)
            {
                return(new Score
                {
                    WinnerTeamId = scores.FirstOrDefault().TeamId,
                    Rule = WinnerRule.MoreRoundsWon
                });
            }
            else if (scores.Select(s => s.CoinsLeft).Distinct().Count() != 1)
            {
                return(new Score
                {
                    WinnerTeamId = scores.FirstOrDefault().TeamId,
                    Rule = WinnerRule.MoreCoinsLeft
                });
            }
            else
            {
                return(new Score
                {
                    WinnerTeamId = 0,
                    Rule = WinnerRule.TrueTie
                });
            }
        }
Esempio n. 11
0
 public int GetCount(PersonaClassification classification)
 {
     return(Rounds.Where(r => r.TrustExchange.PersonaClassification == classification).Count());
 }
Esempio n. 12
0
        public async Task <InputRegistrationResponse> RegisterInputAsync(InputRegistrationRequest request, CancellationToken cancellationToken)
        {
            var coin = await OutpointToCoinAsync(request, cancellationToken).ConfigureAwait(false);

            using (await AsyncLock.LockAsync(cancellationToken).ConfigureAwait(false))
            {
                var round = GetRound(request.RoundId);

                var registeredCoins = Rounds.Where(x => !(x.Phase == Phase.Ended && !x.WasTransactionBroadcast))
                                      .SelectMany(r => r.Alices.Select(a => a.Coin));

                if (registeredCoins.Any(x => x.Outpoint == coin.Outpoint))
                {
                    throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.AliceAlreadyRegistered);
                }

                if (round.IsInputRegistrationEnded(Config.MaxInputCountByRound))
                {
                    throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.WrongPhase);
                }

                if (round is BlameRound blameRound && !blameRound.BlameWhitelist.Contains(coin.Outpoint))
                {
                    throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.InputNotWhitelisted);
                }

                // Compute but don't commit updated CoinJoin to round state, it will
                // be re-calculated on input confirmation. This is computed it here
                // for validation purposes.
                _ = round.Assert <ConstructionState>().AddInput(coin);

                var coinJoinInputCommitmentData = new CoinJoinInputCommitmentData("CoinJoinCoordinatorIdentifier", round.Id);
                if (!OwnershipProof.VerifyCoinJoinInputProof(request.OwnershipProof, coin.TxOut.ScriptPubKey, coinJoinInputCommitmentData))
                {
                    throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.WrongOwnershipProof);
                }

                // Generate a new GUID with the secure random source, to be sure
                // that it is not guessable (Guid.NewGuid() documentation does
                // not say anything about GUID version or randomness source,
                // only that the probability of duplicates is very low).
                var id    = new Guid(Random.GetBytes(16));
                var alice = new Alice(coin, request.OwnershipProof, round, id);

                if (alice.TotalInputAmount < round.MinAmountCredentialValue)
                {
                    throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.NotEnoughFunds);
                }
                if (alice.TotalInputAmount > round.MaxAmountCredentialValue)
                {
                    throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.TooMuchFunds);
                }

                if (alice.TotalInputVsize > round.MaxVsizeAllocationPerAlice)
                {
                    throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.TooMuchVsize);
                }

                var amountCredentialTask = round.AmountCredentialIssuer.HandleRequestAsync(request.ZeroAmountCredentialRequests, cancellationToken);
                var vsizeCredentialTask  = round.VsizeCredentialIssuer.HandleRequestAsync(request.ZeroVsizeCredentialRequests, cancellationToken);

                if (round.RemainingInputVsizeAllocation < round.MaxVsizeAllocationPerAlice)
                {
                    throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.VsizeQuotaExceeded);
                }

                var commitAmountCredentialResponse = await amountCredentialTask.ConfigureAwait(false);

                var commitVsizeCredentialResponse = await vsizeCredentialTask.ConfigureAwait(false);

                alice.SetDeadlineRelativeTo(round.ConnectionConfirmationTimeFrame.Duration);
                round.Alices.Add(alice);

                return(new(alice.Id,
                           commitAmountCredentialResponse,
                           commitVsizeCredentialResponse));
            }
        }
Esempio n. 13
0
        /// <summary>
        /// Returns the rounds in Round not in list roundList based in Id
        /// </summary>
        /// <param name="roundList"></param>
        /// <returns></returns>
        public List <Round> GetRoundsNotInList(IEnumerable <Round> roundList)
        {
            var idList = roundList.Select(p => p.Id).ToList();

            return(Rounds.Where(p => !idList.Contains(p.Id)).ToList());
        }
Esempio n. 14
0
        private async Task ProcessStatusAsync()
        {
            try
            {
                IEnumerable <CcjRunningRoundState> states = await SatoshiClient.GetAllRoundStatesAsync();

                using (await MixLock.LockAsync())
                {
                    foreach (CcjRunningRoundState state in states)
                    {
                        CcjClientRound round = Rounds.SingleOrDefault(x => x.State.RoundId == state.RoundId);
                        if (round == null)                         // It's a new running round.
                        {
                            var r = new CcjClientRound(state);
                            Rounds.Add(r);
                            RoundAdded?.Invoke(this, r);
                        }
                        else
                        {
                            round.State = state;
                            RoundUpdated?.Invoke(this, round);
                        }
                    }

                    var roundsToRemove = new List <long>();
                    foreach (CcjClientRound round in Rounds)
                    {
                        CcjRunningRoundState state = states.SingleOrDefault(x => x.RoundId == round.State.RoundId);
                        if (state == null)                         // The round is not running anymore.
                        {
                            foreach (MixCoin rc in round.CoinsRegistered)
                            {
                                CoinsWaitingForMix.Add(rc);
                            }
                            roundsToRemove.Add(round.State.RoundId);
                        }
                    }

                    foreach (long roundId in roundsToRemove)
                    {
                        Rounds.RemoveAll(x => x.State.RoundId == roundId);
                        RoundRemoved?.Invoke(this, roundId);
                    }
                }

                int delay = new Random().Next(0, 7);                 // delay the response to defend timing attack privacy
                await Task.Delay(TimeSpan.FromSeconds(delay), Stop.Token);

                using (await MixLock.LockAsync())
                {
                    CoinsWaitingForMix.RemoveAll(x => x.SmartCoin.SpenderTransactionId != null);                     // Make sure coins those were somehow spent are removed.

                    CcjClientRound inputRegistrableRound = Rounds.First(x => x.State.Phase == CcjRoundPhase.InputRegistration);
                    if (inputRegistrableRound.AliceUniqueId == null)                     // If didn't register already, check what can we register.
                    {
                        try
                        {
                            var   coinsToRegister             = new List <MixCoin>();
                            var   amountSoFar                 = Money.Zero;
                            Money amountNeededExceptInputFees = inputRegistrableRound.State.Denomination + inputRegistrableRound.State.FeePerOutputs * 2;
                            var   tooSmallInputs              = false;
                            foreach (MixCoin coin in CoinsWaitingForMix
                                     .Where(x => x.SmartCoin.Confirmed || x.SmartCoin.Label.Contains("CoinJoin", StringComparison.Ordinal)) // Where our label contains CoinJoin, CoinJoins can be registered even if not confirmed, our label will likely be CoinJoin only if it was a previous CoinJoin, otherwise the server will refuse us.
                                     .OrderByDescending(y => y.SmartCoin.Amount)                                                            // First order by amount.
                                     .OrderByDescending(z => z.SmartCoin.Confirmed))                                                        // Then order by the amount ordered ienumerable by confirmation, so first try to register confirmed coins.
                            {
                                coinsToRegister.Add(coin);
                                if (inputRegistrableRound.State.MaximumInputCountPerPeer < coinsToRegister.Count)
                                {
                                    tooSmallInputs = true;
                                    break;
                                }

                                amountSoFar += coin.SmartCoin.Amount;
                                if (amountSoFar > amountNeededExceptInputFees + inputRegistrableRound.State.FeePerInputs * coinsToRegister.Count)
                                {
                                    break;
                                }
                            }

                            // If input count doesn't reach the max input registration AND there are enough coins queued, then register to mix.
                            if (!tooSmallInputs && amountSoFar > amountNeededExceptInputFees + inputRegistrableRound.State.FeePerInputs * coinsToRegister.Count)
                            {
                                var changeKey = KeyManager.GenerateNewKey("CoinJoin Change Output", KeyState.Locked, isInternal: true);
                                var activeKey = KeyManager.GenerateNewKey("CoinJoin Active Output", KeyState.Locked, isInternal: true);
                                var blind     = BlindingPubKey.Blind(activeKey.GetP2wpkhScript().ToBytes());

                                var inputProofs = new List <InputProofModel>();
                                foreach (var coin in coinsToRegister)
                                {
                                    var inputProof = new InputProofModel
                                    {
                                        Input = coin.SmartCoin.GetOutPoint(),
                                        Proof = coin.Secret.PrivateKey.SignMessage(ByteHelpers.ToHex(blind.BlindedData))
                                    };
                                    inputProofs.Add(inputProof);
                                }
                                InputsResponse inputsResponse = await AliceClient.PostInputsAsync(changeKey.GetP2wpkhScript(), blind.BlindedData, inputProofs.ToArray());

                                if (!BlindingPubKey.Verify(inputsResponse.BlindedOutputSignature, blind.BlindedData))
                                {
                                    throw new NotSupportedException("Coordinator did not sign the blinded output properly.");
                                }

                                CcjClientRound roundRegistered = Rounds.SingleOrDefault(x => x.State.RoundId == inputsResponse.RoundId);
                                if (roundRegistered == null)
                                {
                                    // If our SatoshiClient doesn't yet know about the round because of the dealy create it.
                                    // Make its state as it'd be the same as our assumed round was, except the roundId and registeredPeerCount, it'll be updated later.
                                    roundRegistered = new CcjClientRound(CcjRunningRoundState.CloneExcept(inputRegistrableRound.State, inputsResponse.RoundId, registeredPeerCount: 1));
                                    Rounds.Add(roundRegistered);
                                    RoundAdded?.Invoke(this, roundRegistered);
                                }

                                foreach (var coin in coinsToRegister)
                                {
                                    roundRegistered.CoinsRegistered.Add(coin);
                                    CoinsWaitingForMix.Remove(coin);
                                }
                                roundRegistered.ActiveOutput       = activeKey;
                                roundRegistered.ChangeOutput       = changeKey;
                                roundRegistered.UnblindedSignature = BlindingPubKey.UnblindSignature(inputsResponse.BlindedOutputSignature, blind.BlindingFactor);
                                roundRegistered.AliceUniqueId      = inputsResponse.UniqueId;
                                RoundUpdated?.Invoke(this, roundRegistered);
                            }
                        }
                        catch (Exception ex)
                        {
                            Logger.LogError <CcjClient>(ex);
                        }
                    }
                    else                     // We registered, let's confirm we're online.
                    {
                        try
                        {
                            string roundHash = await AliceClient.PostConfirmationAsync(inputRegistrableRound.State.RoundId, (Guid)inputRegistrableRound.AliceUniqueId);

                            if (roundHash != null)                             // Then the phase went to connection confirmation.
                            {
                                inputRegistrableRound.RoundHash   = roundHash;
                                inputRegistrableRound.State.Phase = CcjRoundPhase.ConnectionConfirmation;
                                RoundUpdated?.Invoke(this, inputRegistrableRound);
                            }
                        }
                        catch (Exception ex)
                        {
                            Logger.LogError <CcjClient>(ex);
                        }
                    }

                    foreach (CcjClientRound ongoingRound in Rounds.Where(x => x.State.Phase != CcjRoundPhase.InputRegistration && x.AliceUniqueId != null))
                    {
                        try
                        {
                            if (ongoingRound.State.Phase == CcjRoundPhase.ConnectionConfirmation)
                            {
                                if (ongoingRound.RoundHash == null)                                 // If we didn't already obtained our roundHash obtain it.
                                {
                                    string roundHash = await AliceClient.PostConfirmationAsync(inputRegistrableRound.State.RoundId, (Guid)inputRegistrableRound.AliceUniqueId);

                                    if (roundHash == null)
                                    {
                                        throw new NotSupportedException("Coordinator didn't gave us the expected roundHash, even though it's in ConnectionConfirmation phase.");
                                    }
                                    else
                                    {
                                        ongoingRound.RoundHash = roundHash;
                                        RoundUpdated?.Invoke(this, ongoingRound);
                                    }
                                }
                            }
                            else if (ongoingRound.State.Phase == CcjRoundPhase.OutputRegistration)
                            {
                                if (ongoingRound.RoundHash == null)
                                {
                                    throw new NotSupportedException("Coordinator progressed to OutputRegistration phase, even though we didn't obtain roundHash.");
                                }

                                await BobClient.PostOutputAsync(ongoingRound.RoundHash, ongoingRound.ActiveOutput.GetP2wpkhScript(), ongoingRound.UnblindedSignature);
                            }
                            else if (ongoingRound.State.Phase == CcjRoundPhase.Signing)
                            {
                                Transaction unsignedCoinJoin = await AliceClient.GetUnsignedCoinJoinAsync(ongoingRound.State.RoundId, (Guid)ongoingRound.AliceUniqueId);

                                if (NBitcoinHelpers.HashOutpoints(unsignedCoinJoin.Inputs.Select(x => x.PrevOut)) != ongoingRound.RoundHash)
                                {
                                    throw new NotSupportedException("Coordinator provided invalid roundHash.");
                                }
                                Money amountBack = unsignedCoinJoin.Outputs
                                                   .Where(x => x.ScriptPubKey == ongoingRound.ActiveOutput.GetP2wpkhScript() || x.ScriptPubKey == ongoingRound.ChangeOutput.GetP2wpkhScript())
                                                   .Sum(y => y.Value);
                                Money minAmountBack = ongoingRound.CoinsRegistered.Sum(x => x.SmartCoin.Amount);                                              // Start with input sum.
                                minAmountBack -= ongoingRound.State.FeePerOutputs * 2 + ongoingRound.State.FeePerInputs * ongoingRound.CoinsRegistered.Count; // Minus miner fee.
                                Money actualDenomination     = unsignedCoinJoin.GetIndistinguishableOutputs().OrderByDescending(x => x.count).First().value;  // Denomination may grow.
                                Money expectedCoordinatorFee = new Money((ongoingRound.State.CoordinatorFeePercent * 0.01m) * decimal.Parse(actualDenomination.ToString(false, true)), MoneyUnit.BTC);
                                minAmountBack -= expectedCoordinatorFee;                                                                                      // Minus expected coordinator fee.

                                // If there's no change output then coordinator protection may happened:
                                if (unsignedCoinJoin.Outputs.All(x => x.ScriptPubKey != ongoingRound.ChangeOutput.GetP2wpkhScript()))
                                {
                                    Money minimumOutputAmount      = new Money(0.0001m, MoneyUnit.BTC);                                             // If the change would be less than about $1 then add it to the coordinator.
                                    Money onePercentOfDenomination = new Money(actualDenomination.ToDecimal(MoneyUnit.BTC) * 0.01m, MoneyUnit.BTC); // If the change is less than about 1% of the newDenomination then add it to the coordinator fee.
                                    Money minimumChangeAmount      = Math.Max(minimumOutputAmount, onePercentOfDenomination);

                                    minAmountBack -= minimumChangeAmount;                                     // Minus coordinator protections (so it won't create bad coinjoins.)
                                }

                                if (amountBack < minAmountBack)
                                {
                                    throw new NotSupportedException("Coordinator did not add enough value to our outputs in the coinjoin.");
                                }

                                new TransactionBuilder()
                                .AddKeys(ongoingRound.CoinsRegistered.Select(x => x.Secret).ToArray())
                                .AddCoins(ongoingRound.CoinsRegistered.Select(x => x.SmartCoin.GetCoin()))
                                .SignTransactionInPlace(unsignedCoinJoin, SigHash.All);

                                var myDic = new Dictionary <int, WitScript>();

                                for (int i = 0; i < unsignedCoinJoin.Inputs.Count; i++)
                                {
                                    var input = unsignedCoinJoin.Inputs[i];
                                    if (ongoingRound.CoinsRegistered.Select(x => x.SmartCoin.GetOutPoint()).Contains(input.PrevOut))
                                    {
                                        myDic.Add(i, unsignedCoinJoin.Inputs[i].WitScript);
                                    }
                                }

                                await AliceClient.PostSignaturesAsync(ongoingRound.State.RoundId, (Guid)ongoingRound.AliceUniqueId, myDic);
                            }
                        }
                        catch (Exception ex)
                        {
                            Logger.LogError <CcjClient>(ex);
                        }
                    }
                }
            }
            catch (TaskCanceledException ex)
            {
                Logger.LogTrace <CcjClient>(ex);
            }
            catch (Exception ex)
            {
                Logger.LogError <CcjClient>(ex);
            }
        }