public IEnumerable <CcjRound> GetRunningRounds() { using (RoundsListLock.Lock()) { return(Rounds.Where(x => x.Status == CcjRoundStatus.Running).ToArray()); } }
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); } } }
public void FailAllRoundsInInputRegistration() { using (RoundsListLock.Lock()) { foreach (var r in Rounds.Where(x => x.Status == CcjRoundStatus.Running && x.Phase == CcjRoundPhase.InputRegistration)) { r.Fail(); } } }
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); } } }
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); }
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."); } }
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."); } } }
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); }
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); } }
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 }); } }
public int GetCount(PersonaClassification classification) { return(Rounds.Where(r => r.TrustExchange.PersonaClassification == classification).Count()); }
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)); } }
/// <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()); }
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); } }