public BlameRound(RoundParameters roundParameters, Round blameOf, ISet <OutPoint> blameWhitelist) : base(roundParameters) { BlameOf = blameOf; BlameWhitelist = blameWhitelist; InputRegistrationTimeFrame = TimeFrame.Create(RoundParameters.BlameInputRegistrationTimeout).StartNow(); }
public BlameRound(RoundParameters parameters, Round blameOf, ISet <OutPoint> blameWhitelist, WasabiRandom random) : base(parameters, random) { BlameOf = blameOf; BlameWhitelist = blameWhitelist; InputRegistrationTimeFrame = TimeFrame.Create(Parameters.BlameInputRegistrationTimeout).StartNow(); }
private void LogCoinJoinSummary(ImmutableArray <AliceClient> registeredAliceClients, IEnumerable <TxOut> myOutputs, Transaction unsignedCoinJoinTransaction, RoundState roundState) { RoundParameters roundParameters = roundState.CoinjoinState.Parameters; FeeRate feeRate = roundParameters.MiningFeeRate; var totalEffectiveInputAmount = Money.Satoshis(registeredAliceClients.Sum(a => a.EffectiveValue)); var totalEffectiveOutputAmount = Money.Satoshis(myOutputs.Sum(a => a.Value - feeRate.GetFee(a.ScriptPubKey.EstimateOutputVsize()))); var effectiveDifference = totalEffectiveInputAmount - totalEffectiveOutputAmount; var totalInputAmount = Money.Satoshis(registeredAliceClients.Sum(a => a.SmartCoin.Amount)); var totalOutputAmount = Money.Satoshis(myOutputs.Sum(a => a.Value)); var totalDifference = Money.Satoshis(totalInputAmount - totalOutputAmount); var inputNetworkFee = Money.Satoshis(registeredAliceClients.Sum(alice => feeRate.GetFee(alice.SmartCoin.Coin.ScriptPubKey.EstimateInputVsize()))); var outputNetworkFee = Money.Satoshis(myOutputs.Sum(output => feeRate.GetFee(output.ScriptPubKey.EstimateOutputVsize()))); var totalNetworkFee = inputNetworkFee + outputNetworkFee; var totalCoordinationFee = Money.Satoshis(registeredAliceClients.Where(a => a.IsPayingZeroCoordinationFee).Sum(a => roundParameters.CoordinationFeeRate.GetFee(a.SmartCoin.Amount))); string[] summary = new string[] { "", $"\tInput total: {totalInputAmount.ToString(true, false)} Eff: {totalEffectiveInputAmount.ToString(true, false)} NetwFee: {inputNetworkFee.ToString(true, false)} CoordFee: {totalCoordinationFee.ToString(true)}", $"\tOutpu total: {totalOutputAmount.ToString(true, false)} Eff: {totalEffectiveOutputAmount.ToString(true, false)} NetwFee: {outputNetworkFee.ToString(true, false)}", $"\tTotal diff : {totalDifference.ToString(true, false)}", $"\tEffec diff : {effectiveDifference.ToString(true, false)}", $"\tTotal fee : {totalNetworkFee.ToString(true, false)}" }; roundState.LogDebug(string.Join(Environment.NewLine, summary)); }
public virtual RoundParameters CreateBlameRoundParameter(FeeRate feeRate, Round blameOf) => RoundParameters.Create( Config, Network, feeRate, Config.CoordinationFeeRate, blameOf.Parameters.MaxSuggestedAmount);
public virtual RoundParameters CreateRoundParameter(FeeRate feeRate, int connectionConfirmationStartedCounter) => RoundParameters.Create( Config, Network, feeRate, Config.CoordinationFeeRate, MaxSuggestedAmountProvider.GetMaxSuggestedAmount(connectionConfirmationStartedCounter));
private static bool TryAddGroup(RoundParameters parameters, Dictionary <int, IEnumerable <SmartCoin> > groups, IEnumerable <SmartCoin> group) { var inSum = group.Sum(x => x.EffectiveValue(parameters.MiningFeeRate, parameters.CoordinationFeeRate)); var outFee = parameters.MiningFeeRate.GetFee(Constants.P2wpkhOutputVirtualSize); if (inSum >= outFee + parameters.AllowedOutputAmounts.Min) { var k = HashCode.Combine(group.OrderBy(x => x.TransactionId).ThenBy(x => x.Index)); return(groups.TryAdd(k, group)); } return(false); }
public async Task <CoinJoinResult> StartCoinJoinAsync(IEnumerable <SmartCoin> coinCandidates, CancellationToken cancellationToken) { var tryLimit = 6; RoundState?currentRoundState; uint256 excludeRound = uint256.Zero; ImmutableList <SmartCoin> coins; do { currentRoundState = await WaitForRoundAsync(excludeRound, cancellationToken).ConfigureAwait(false); RoundParameters roundParameteers = currentRoundState.CoinjoinState.Parameters; coins = SelectCoinsForRound(coinCandidates, roundParameteers, ConsolidationMode, AnonScoreTarget, SecureRandom); if (roundParameteers.MaxSuggestedAmount != default && coins.Any(c => c.Amount > roundParameteers.MaxSuggestedAmount)) { excludeRound = currentRoundState.Id; currentRoundState.LogInfo($"Skipping the round for more optimal mixing. Max suggested amount is '{roundParameteers.MaxSuggestedAmount}' BTC, biggest coin amount is: '{coins.Select(c => c.Amount).Max()}' BTC."); continue; } break; }while (!cancellationToken.IsCancellationRequested); if (coins.IsEmpty) { throw new NoCoinsToMixException($"No coin was selected from '{coinCandidates.Count()}' number of coins. Probably it was not economical, total amount of coins were: {Money.Satoshis(coinCandidates.Sum(c => c.Amount))} BTC."); } for (var tries = 0; tries < tryLimit; tries++) { CoinJoinResult result = await StartRoundAsync(coins, currentRoundState, cancellationToken).ConfigureAwait(false); if (!result.GoForBlameRound) { return(result); } // Only use successfully registered coins in the blame round. coins = result.RegisteredCoins; currentRoundState.LogInfo("Waiting for the blame round."); currentRoundState = await WaitForBlameRoundAsync(currentRoundState.Id, cancellationToken).ConfigureAwait(false); } throw new InvalidOperationException("Blame rounds were not successful."); }
public Round(RoundParameters parameters, WasabiRandom random) { Parameters = parameters; CoinjoinState = new ConstructionState(Parameters); AmountCredentialIssuer = new(new(random), random, Parameters.MaxAmountCredentialValue); VsizeCredentialIssuer = new(new(random), random, Parameters.MaxVsizeCredentialValue); AmountCredentialIssuerParameters = AmountCredentialIssuer.CredentialIssuerSecretKey.ComputeCredentialIssuerParameters(); VsizeCredentialIssuerParameters = VsizeCredentialIssuer.CredentialIssuerSecretKey.ComputeCredentialIssuerParameters(); InputRegistrationTimeFrame = TimeFrame.Create(Parameters.StandardInputRegistrationTimeout).StartNow(); ConnectionConfirmationTimeFrame = TimeFrame.Create(Parameters.ConnectionConfirmationTimeout); OutputRegistrationTimeFrame = TimeFrame.Create(Parameters.OutputRegistrationTimeout); TransactionSigningTimeFrame = TimeFrame.Create(Parameters.TransactionSigningTimeout); Id = CalculateHash(); }
public Round(RoundParameters roundParameters) { RoundParameters = roundParameters; var allowedAmounts = new MoneyRange(roundParameters.MinRegistrableAmount, RoundParameters.MaxRegistrableAmount); var txParams = new MultipartyTransactionParameters(roundParameters.FeeRate, roundParameters.CoordinationFeeRate, allowedAmounts, allowedAmounts, roundParameters.Network); CoinjoinState = new ConstructionState(txParams); InitialInputVsizeAllocation = CoinjoinState.Parameters.MaxTransactionSize - MultipartyTransactionParameters.SharedOverhead; MaxVsizeCredentialValue = Math.Min(InitialInputVsizeAllocation / RoundParameters.MaxInputCountByRound, (int)ProtocolConstants.MaxVsizeCredentialValue); MaxVsizeAllocationPerAlice = MaxVsizeCredentialValue; AmountCredentialIssuer = new(new(RoundParameters.Random), RoundParameters.Random, MaxAmountCredentialValue); VsizeCredentialIssuer = new(new(RoundParameters.Random), RoundParameters.Random, MaxVsizeCredentialValue); AmountCredentialIssuerParameters = AmountCredentialIssuer.CredentialIssuerSecretKey.ComputeCredentialIssuerParameters(); VsizeCredentialIssuerParameters = VsizeCredentialIssuer.CredentialIssuerSecretKey.ComputeCredentialIssuerParameters(); InputRegistrationTimeFrame = TimeFrame.Create(RoundParameters.StandardInputRegistrationTimeout).StartNow(); ConnectionConfirmationTimeFrame = TimeFrame.Create(RoundParameters.ConnectionConfirmationTimeout); OutputRegistrationTimeFrame = TimeFrame.Create(RoundParameters.OutputRegistrationTimeout); TransactionSigningTimeFrame = TimeFrame.Create(RoundParameters.TransactionSigningTimeout); }
public async Task NonStandardOutputAsync() { WabiSabiConfig cfg = new(); RoundParameters parameters = WabiSabiFactory.CreateRoundParameters(cfg) with { MaxVsizeAllocationPerAlice = 11 + 31 + MultipartyTransactionParameters.SharedOverhead + 13 }; var round = WabiSabiFactory.CreateRound(parameters); using Arena arena = await ArenaBuilder.From(cfg).CreateAndStartAsync(round); round.SetPhase(Phase.OutputRegistration); round.Alices.Add(WabiSabiFactory.CreateAlice(Money.Coins(1), round)); var sha256Bounty = Script.FromHex("aa20000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f87"); var req = WabiSabiFactory.CreateOutputRegistrationRequest(round, sha256Bounty); var ex = await Assert.ThrowsAsync <WabiSabiProtocolException>(async() => await arena.RegisterOutputAsync(req, CancellationToken.None)); // The following assertion requires standardness to be checked before allowed script types Assert.Equal(WabiSabiProtocolErrorCode.NonStandardOutput, ex.ErrorCode); await arena.StopAsync(CancellationToken.None); }
public async Task ScriptNotAllowedAsync() { WabiSabiConfig cfg = new(); RoundParameters parameters = WabiSabiFactory.CreateRoundParameters(cfg) with { MaxVsizeAllocationPerAlice = 11 + 34 + MultipartyTransactionParameters.SharedOverhead }; var round = WabiSabiFactory.CreateRound(parameters); using Arena arena = await ArenaBuilder.From(cfg).CreateAndStartAsync(round); using Key key = new(); round.SetPhase(Phase.OutputRegistration); round.Alices.Add(WabiSabiFactory.CreateAlice(Money.Coins(1), round)); var req = WabiSabiFactory.CreateOutputRegistrationRequest(round, key.PubKey.GetAddress(ScriptPubKeyType.Legacy, Network.Main).ScriptPubKey); var ex = await Assert.ThrowsAsync <WabiSabiProtocolException>(async() => await arena.RegisterOutputAsync(req, CancellationToken.None)); Assert.Equal(WabiSabiProtocolErrorCode.ScriptNotAllowed, ex.ErrorCode); await arena.StopAsync(CancellationToken.None); }
internal static ImmutableList <SmartCoin> SelectCoinsForRound(IEnumerable <SmartCoin> coins, RoundParameters parameters, bool consolidationMode, int anonScoreTarget, WasabiRandom rnd) { var filteredCoins = coins .Where(x => parameters.AllowedInputAmounts.Contains(x.Amount)) .Where(x => parameters.AllowedInputTypes.Any(t => x.ScriptPubKey.IsScriptType(t))) .Where(x => x.EffectiveValue(parameters.MiningFeeRate) > Money.Zero) .ToShuffled() .ToArray(); var privateCoins = filteredCoins .Where(x => x.HdPubKey.AnonymitySet >= anonScoreTarget) .ToArray(); var nonPrivateCoins = filteredCoins .Where(x => x.HdPubKey.AnonymitySet < anonScoreTarget) .ToArray(); // Make sure it's ordered by 1 private and 1 non-private coins. // Otherwise we'd keep mixing private coins too much during the end of our mixing sessions. var organizedCoins = new List <SmartCoin>(); for (int i = 0; i < Math.Max(privateCoins.Length, nonPrivateCoins.Length); i++) { if (i < nonPrivateCoins.Length) { var npc = nonPrivateCoins[i]; organizedCoins.Add(npc); } if (i < privateCoins.Length) { var pc = privateCoins[i]; organizedCoins.Add(pc); } } // How many inputs do we want to provide to the mix? int inputCount = Math.Min( organizedCoins.Count, consolidationMode ? MaxInputsRegistrableByWallet : GetInputTarget(nonPrivateCoins.Length, privateCoins.Length, rnd)); // Always use the largest amounts, so we do not participate with insignificant amounts and fragment wallet needlessly. var largestAmounts = nonPrivateCoins .OrderByDescending(x => x.Amount) .Take(3) .ToArray(); // Select a group of coins those are close to each other by anonymity score. Dictionary <int, IEnumerable <SmartCoin> > groups = new(); // Create a bunch of combinations. var sw1 = Stopwatch.StartNew(); foreach (var coin in largestAmounts) { var baseGroup = organizedCoins.Except(new[] { coin }).Take(inputCount - 1).Concat(new[] { coin }); TryAddGroup(parameters, groups, baseGroup); var sw2 = Stopwatch.StartNew(); foreach (var group in organizedCoins .Except(new[] { coin }) .CombinationsWithoutRepetition(inputCount - 1) .Select(x => x.Concat(new[] { coin }))) { TryAddGroup(parameters, groups, group); if (sw2.Elapsed > TimeSpan.FromSeconds(1)) { break; } } sw2.Reset(); if (sw1.Elapsed > TimeSpan.FromSeconds(10)) { break; } } if (!groups.Any()) { return(ImmutableList <SmartCoin> .Empty); } // Select the group where the less coins coming from the same tx. var bestRep = groups.Values.Select(x => GetReps(x)).Min(x => x); var bestRepGroups = groups.Values.Where(x => GetReps(x) == bestRep); var remainingLargestAmounts = bestRepGroups .Select(x => x.OrderByDescending(x => x.Amount).First()) .ToHashSet(); var largestAmount = remainingLargestAmounts.RandomElement(); var finalCandidate = bestRepGroups .Where(x => x.OrderByDescending(x => x.Amount).First() == largestAmount) .RandomElement(); return(finalCandidate?.ToShuffled()?.ToImmutableList() ?? ImmutableList <SmartCoin> .Empty); }