Example #1
0
 public BlameRound(RoundParameters roundParameters, Round blameOf, ISet <OutPoint> blameWhitelist)
     : base(roundParameters)
 {
     BlameOf                    = blameOf;
     BlameWhitelist             = blameWhitelist;
     InputRegistrationTimeFrame = TimeFrame.Create(RoundParameters.BlameInputRegistrationTimeout).StartNow();
 }
Example #2
0
 public BlameRound(RoundParameters parameters, Round blameOf, ISet <OutPoint> blameWhitelist, WasabiRandom random)
     : base(parameters, random)
 {
     BlameOf                    = blameOf;
     BlameWhitelist             = blameWhitelist;
     InputRegistrationTimeFrame = TimeFrame.Create(Parameters.BlameInputRegistrationTimeout).StartNow();
 }
Example #3
0
    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));
Example #6
0
    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);
    }
Example #7
0
    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.");
    }
Example #8
0
    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();
    }
Example #9
0
    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);
    }
Example #10
0
    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);
    }
Example #11
0
    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);
    }
Example #12
0
    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);
    }