public async Task ExecuteNextPhaseAsync(CcjRoundPhase expectedPhase, Money feePerInputs = null, Money feePerOutputs = null) { using (await RoundSynchronizerLock.LockAsync()) { try { Logger.LogInfo <CcjRound>($"Round ({RoundId}): Phase change requested: {expectedPhase.ToString()}."); if (Status == CcjRoundStatus.NotStarted) // So start the input registration phase { if (expectedPhase != CcjRoundPhase.InputRegistration) { return; } // Calculate fees. if (feePerInputs is null || feePerOutputs is null) { (Money feePerInputs, Money feePerOutputs)fees = await CalculateFeesAsync(RpcClient, ConfirmationTarget); FeePerInputs = feePerInputs ?? fees.feePerInputs; FeePerOutputs = feePerOutputs ?? fees.feePerOutputs; } else { FeePerInputs = feePerInputs; FeePerOutputs = feePerOutputs; } Status = CcjRoundStatus.Running; } else if (Status != CcjRoundStatus.Running) // Aborted or succeeded, swallow { return; } else if (Phase == CcjRoundPhase.InputRegistration) { if (expectedPhase != CcjRoundPhase.ConnectionConfirmation) { return; } Phase = CcjRoundPhase.ConnectionConfirmation; } else if (Phase == CcjRoundPhase.ConnectionConfirmation) { if (expectedPhase != CcjRoundPhase.OutputRegistration) { return; } Phase = CcjRoundPhase.OutputRegistration; } else if (Phase == CcjRoundPhase.OutputRegistration) { if (expectedPhase != CcjRoundPhase.Signing) { return; } // Build CoinJoin: Money newDenomination = CalculateNewDenomination(); var transaction = Network.Consensus.ConsensusFactory.CreateTransaction(); // 2. Add Bob outputs. foreach (Bob bob in Bobs.Where(x => x.Level == MixingLevels.GetBaseLevel())) { transaction.Outputs.AddWithOptimize(newDenomination, bob.ActiveOutputAddress.ScriptPubKey); } // 2.1 newDenomination may differs from the Denomination at registration, so we may not be able to tinker with // additional outputs. bool tinkerWithAdditionalMixingLevels = CanUseAdditionalOutputs(newDenomination); if (tinkerWithAdditionalMixingLevels) { foreach (MixingLevel level in MixingLevels.GetLevelsExceptBase()) { IEnumerable <Bob> bobsOnThisLevel = Bobs.Where(x => x.Level == level); if (bobsOnThisLevel.Count() <= 1) { break; } foreach (Bob bob in bobsOnThisLevel) { transaction.Outputs.AddWithOptimize(level.Denomination, bob.ActiveOutputAddress.ScriptPubKey); } } } BitcoinWitPubKeyAddress coordinatorAddress = Constants.GetCoordinatorAddress(Network); // 3. If there are less Bobs than Alices, then add our own address. The malicious Alice, who will refuse to sign. for (int i = 0; i < MixingLevels.Count(); i++) { var aliceCountInLevel = Alices.Count(x => i < x.BlindedOutputScripts.Length); var missingBobCount = aliceCountInLevel - Bobs.Count(x => x.Level == MixingLevels.GetLevel(i)); for (int j = 0; j < missingBobCount; j++) { var denomination = MixingLevels.GetLevel(i).Denomination; transaction.Outputs.AddWithOptimize(denomination, coordinatorAddress); } } // 4. Start building Coordinator fee. var baseDenominationOutputCount = transaction.Outputs.Count(x => x.Value == newDenomination); Money coordinatorBaseFeePerAlice = newDenomination.Percentage(CoordinatorFeePercent * baseDenominationOutputCount); Money coordinatorFee = baseDenominationOutputCount * coordinatorBaseFeePerAlice; if (tinkerWithAdditionalMixingLevels) { foreach (MixingLevel level in MixingLevels.GetLevelsExceptBase()) { var denominationOutputCount = transaction.Outputs.Count(x => x.Value == level.Denomination); if (denominationOutputCount <= 1) { break; } Money coordinatorLevelFeePerAlice = level.Denomination.Percentage(CoordinatorFeePercent * denominationOutputCount); coordinatorFee += coordinatorLevelFeePerAlice * denominationOutputCount; } } // 5. Add the inputs and the changes of Alices. var spentCoins = new List <Coin>(); foreach (Alice alice in Alices) { foreach (var input in alice.Inputs) { transaction.Inputs.Add(new TxIn(input.Outpoint)); spentCoins.Add(input); } Money changeAmount = alice.InputSum - alice.NetworkFeeToPay - newDenomination - coordinatorBaseFeePerAlice; if (tinkerWithAdditionalMixingLevels) { for (int i = 1; i < alice.BlindedOutputScripts.Length; i++) { MixingLevel level = MixingLevels.GetLevel(i); var denominationOutputCount = transaction.Outputs.Count(x => x.Value == level.Denomination); if (denominationOutputCount <= 1) { break; } changeAmount -= (level.Denomination + FeePerOutputs + (level.Denomination.Percentage(CoordinatorFeePercent * denominationOutputCount))); } } if (changeAmount > Money.Zero) // If the coordinator fee would make change amount to be negative or zero then no need to pay it. { Money minimumOutputAmount = Money.Coins(0.0001m); // If the change would be less than about $1 then add it to the coordinator. Money onePercentOfDenomination = newDenomination.Percentage(1m); // If the change is less than about 1% of the newDenomination then add it to the coordinator fee. Money minimumChangeAmount = Math.Max(minimumOutputAmount, onePercentOfDenomination); if (changeAmount < minimumChangeAmount) { coordinatorFee += changeAmount; } else { transaction.Outputs.AddWithOptimize(changeAmount, alice.ChangeOutputAddress.ScriptPubKey); } } else { // Alice has no money enough to pay the coordinator fee then allow her to pay what she can. coordinatorFee += changeAmount; } } // 6. Add Coordinator fee only if > about $3, else just let it to be miner fee. if (coordinatorFee > Money.Coins(0.0003m)) { transaction.Outputs.AddWithOptimize(coordinatorFee, coordinatorAddress); } // 7. Create the unsigned transaction. var builder = Network.CreateTransactionBuilder(); UnsignedCoinJoin = builder .ContinueToBuild(transaction) .AddCoins(spentCoins) // It makes sure the UnsignedCoinJoin goes through TransactionBuilder optimizations. .BuildTransaction(false); // 8. Try optimize fees. await OptimizeFeesAsync(spentCoins); SignedCoinJoin = Transaction.Parse(UnsignedCoinJoin.ToHex(), Network); Phase = CcjRoundPhase.Signing; } else { return; } Logger.LogInfo <CcjRound>($"Round ({RoundId}): Phase initialized: {expectedPhase.ToString()}."); }
public Bob(BitcoinAddress activeOutputAddress, MixingLevel level) { ActiveOutputAddress = Guard.NotNull(nameof(activeOutputAddress), activeOutputAddress); Level = Guard.NotNull(nameof(level), level); }