Exemple #1
0
        CreateRoundWithOutputsReadyToSignAsync(Arena arena, Key key1, SmartCoin coin1, Key key2, SmartCoin coin2)
        {
            // Create the round.
            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

            var round = Assert.Single(arena.Rounds);

            round.MaxVsizeAllocationPerAlice = 11 + 31 + MultipartyTransactionParameters.SharedOverhead;
            var arenaClient = WabiSabiFactory.CreateArenaClient(arena);

            // Register Alices.
            using RoundStateUpdater roundStateUpdater = new(TimeSpan.FromSeconds(2), arena);
            await roundStateUpdater.StartAsync(CancellationToken.None);

            using var identificationKey = new Key();
            var task1 = AliceClient.CreateRegisterAndConfirmInputAsync(RoundState.FromRound(round), arenaClient, coin1, key1.GetBitcoinSecret(round.Network), identificationKey, roundStateUpdater, CancellationToken.None);
            var task2 = AliceClient.CreateRegisterAndConfirmInputAsync(RoundState.FromRound(round), arenaClient, coin2, key2.GetBitcoinSecret(round.Network), identificationKey, roundStateUpdater, CancellationToken.None);

            while (Phase.OutputRegistration != round.Phase)
            {
                await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));
            }

            await Task.WhenAll(task1, task2);

            var aliceClient1 = task1.Result;
            var aliceClient2 = task2.Result;

            // Register outputs.
            var bobClient = new BobClient(round.Id, arenaClient);

            using var destKey1 = new Key();
            using var destKey2 = new Key();
            await bobClient.RegisterOutputAsync(
                destKey1.PubKey.WitHash.ScriptPubKey,
                aliceClient1.IssuedAmountCredentials.Take(ProtocolConstants.CredentialNumber),
                aliceClient1.IssuedVsizeCredentials.Take(ProtocolConstants.CredentialNumber),
                CancellationToken.None).ConfigureAwait(false);

            await bobClient.RegisterOutputAsync(
                destKey1.PubKey.WitHash.ScriptPubKey,
                aliceClient2.IssuedAmountCredentials.Take(ProtocolConstants.CredentialNumber),
                aliceClient2.IssuedVsizeCredentials.Take(ProtocolConstants.CredentialNumber),
                CancellationToken.None).ConfigureAwait(false);

            await roundStateUpdater.StopAsync(CancellationToken.None);

            return(round, aliceClient1, aliceClient2);
        }
Exemple #2
0
        public void Test1()
        {
            var registry = new CoinsRegistry();
            var key0     = new Key();
            var key1     = new Key();

            var stx0 = TransactionBuilders.CreateCreditingTransaction(key0.PubKey.Hash, Money.Coins(2));
            var stx1 = TransactionBuilders.CreateCreditingTransaction(key1.PubKey.Hash, Money.Coins(5));

            var coin0 = new SmartCoin(stx0, 0);

            Assert.True(registry.TryAdd(coin0));
            Assert.False(registry.TryAdd(coin0));
            Assert.False(registry.TryAdd(coin0.AsSpentBy(uint256.One)));

            var registeredCoin = Assert.Single(registry.AsCoinsView());

            Assert.Equal(registeredCoin, coin0);
            Assert.True(registeredCoin.Unspent);

            var coin1 = new SmartCoin(stx1, 0);

            Assert.True(registry.TryAdd(coin1));
            ////////////

            var(key2, keyChange) = (new Key(), new Key());
            var stx2 = TransactionBuilders.CreateSpendingTransaction(new[] { coin0.GetCoin(), coin1.GetCoin() }, key2.PubKey.Hash, keyChange.PubKey.Hash);

            registry.Spend(coin0, stx2.GetHash());
            registry.Spend(coin1, stx2.GetHash());
            var spentCoin = Assert.Single(registry.AsCoinsView(), coin => coin == coin0);

            Assert.False(spentCoin.Unspent);
            Assert.Equal(spentCoin, coin0);

            var coin2 = new SmartCoin(stx2, 0);
            var coin3 = new SmartCoin(stx2, 1);

            Assert.True(registry.TryAdd(coin2));
            Assert.True(registry.TryAdd(coin3));
            ////////////

            var changes = registry.Undo(stx0.GetHash());

            Assert.Empty(registry);
            var removedCoin = Assert.Single(changes.toRemove);

            Assert.Empty(changes.toAdd);
        }
Exemple #3
0
        public CoinViewModel(SmartCoin model)
        {
            Model = model;

            model.WhenAnyValue(x => x.Confirmed).ObserveOn(RxApp.MainThreadScheduler).Subscribe(confirmed =>
            {
                RefreshSmartCoinStatus();
                this.RaisePropertyChanged(nameof(Confirmed));
            });

            model.WhenAnyValue(x => x.SpentOrCoinJoinInProgress).ObserveOn(RxApp.MainThreadScheduler).Subscribe(_ =>
            {
                this.RaisePropertyChanged(nameof(SpentOrCoinJoinInProgress));
            });

            model.WhenAnyValue(x => x.CoinJoinInProgress).ObserveOn(RxApp.MainThreadScheduler).Subscribe(_ =>
            {
                RefreshSmartCoinStatus();
                this.RaisePropertyChanged(nameof(CoinJoinInProgress));
            });

            model.WhenAnyValue(x => x.IsBanned).ObserveOn(RxApp.MainThreadScheduler).Subscribe(_ =>
            {
                RefreshSmartCoinStatus();
            });

            model.WhenAnyValue(x => x.SpentAccordingToBackend).ObserveOn(RxApp.MainThreadScheduler).Subscribe(_ =>
            {
                RefreshSmartCoinStatus();
            });

            model.WhenAnyValue(x => x.Unspent).ObserveOn(RxApp.MainThreadScheduler).Subscribe(_ =>
            {
                this.RaisePropertyChanged(nameof(Unspent));
            });

            this.WhenAnyValue(x => x.Status).Subscribe(_ =>
            {
                this.RaisePropertyChanged(nameof(ToolTip));
            });

            Global.Synchronizer.WhenAnyValue(x => x.BestBlockchainHeight).ObserveOn(RxApp.MainThreadScheduler).Subscribe(_ =>
            {
                RefreshSmartCoinStatus();
                this.RaisePropertyChanged(nameof(Confirmations));
            });

            Global.ChaumianClient.StateUpdated += ChaumianClient_StateUpdated;
        }
Exemple #4
0
        public CoinViewModel(ChaincaseWalletManager walletManager, Config config, BitcoinStore bitcoinStore, SmartCoin model)
        {
            _walletManager = walletManager;
            _config        = config;
            _bitcoinStore  = bitcoinStore;
            Model          = model;

            Disposables = new CompositeDisposable();

            _coinJoinInProgress = Model.WhenAnyValue(x => x.CoinJoinInProgress)
                                  .ObserveOn(RxApp.MainThreadScheduler)
                                  .ToProperty(this, x => x.CoinJoinInProgress)
                                  .DisposeWith(Disposables);

            _unspent = Model.WhenAnyValue(x => x.Unspent).ToProperty(this, x => x.Unspent, scheduler: RxApp.MainThreadScheduler)
                       .DisposeWith(Disposables);

            _confirmed = Model.WhenAnyValue(x => x.Confirmed).ToProperty(this, x => x.Confirmed, scheduler: RxApp.MainThreadScheduler)
                         .DisposeWith(Disposables);

            _unavailable = Model.WhenAnyValue(x => x.Unavailable).ToProperty(this, x => x.Unavailable, scheduler: RxApp.MainThreadScheduler)
                           .DisposeWith(Disposables);

            this.WhenAnyValue(x => x.Status)
            .ObserveOn(RxApp.MainThreadScheduler)
            .Subscribe(_ => this.RaisePropertyChanged(nameof(ToolTip)));

            _cluster = Model
                       .WhenAnyValue(x => x.Clusters, x => x.Clusters.Labels)
                       .Select(x => x.Item2.ToString())
                       .ToProperty(this, x => x.Clusters, scheduler: RxApp.MainThreadScheduler)
                       .DisposeWith(Disposables);

            Observable
            .Merge(Model.WhenAnyValue(x => x.IsBanned, x => x.SpentAccordingToBackend, x => x.CoinJoinInProgress).Select(_ => Unit.Default))
            .Merge(Observable.FromEventPattern(_walletManager.CurrentWallet.ChaumianClient, nameof(_walletManager.CurrentWallet.ChaumianClient.StateUpdated)).Select(_ => Unit.Default))
            .ObserveOn(RxApp.MainThreadScheduler)
            .Subscribe(_ => RefreshSmartCoinStatus())
            .DisposeWith(Disposables);

            _bitcoinStore.SmartHeaderChain
            .WhenAnyValue(x => x.TipHeight).Select(_ => Unit.Default)
            .Merge(Model.WhenAnyValue(x => x.Height).Select(_ => Unit.Default))
            .Throttle(TimeSpan.FromSeconds(0.1))                     // DO NOT TAKE THIS THROTTLE OUT, OTHERWISE SYNCING WITH COINS IN THE WALLET WILL STACKOVERFLOW!
            .ObserveOn(RxApp.MainThreadScheduler)
            .Subscribe(_ => this.RaisePropertyChanged(nameof(Confirmations)))
            .DisposeWith(Disposables);
        }
Exemple #5
0
        /// <returns>If the selection was successful. If there's enough coins to spend from.</returns>
        private bool TrySelectCoins(HashSet <SmartCoin> coinsToSpend, Money totalOutAmount, IEnumerable <SmartCoin> unspentCoins)
        {
            // If there's no need for input merging, then use the largest selected.
            // Do not prefer anonymity set. You can assume the user prefers anonymity set manually through the GUI.
            SmartCoin largestCoin = unspentCoins.OrderByDescending(x => x.Amount).FirstOrDefault();

            if (largestCoin == default)
            {
                return(false); // If there's no coin then unsuccessful selection.
            }
            else               // Check if we can do without input merging.
            {
                var largestCoins = unspentCoins.Where(x => x.ScriptPubKey == largestCoin.ScriptPubKey);

                if (largestCoins.Sum(x => x.Amount) >= totalOutAmount)
                {
                    foreach (var c in largestCoins)
                    {
                        coinsToSpend.Add(c);
                    }
                    return(true);
                }
            }

            // If there's a need for input merging.
            foreach (var coin in unspentCoins
                     .OrderByDescending(x => x.AnonymitySet)      // Always try to spend/merge the largest anonset coins first.
                     .ThenByDescending(x => x.Amount))            // Then always try to spend by amount.
            {
                coinsToSpend.Add(coin);
                // If reaches the amount, then return true, else just go with the largest coin.
                if (coinsToSpend.Select(x => x.Amount).Sum() >= totalOutAmount)
                {
                    // Add if we can find address reuse.
                    foreach (var c in unspentCoins
                             .Except(coinsToSpend)                                                 // So we're choosing from the non selected coins.
                             .Where(x => coinsToSpend.Any(y => y.ScriptPubKey == x.ScriptPubKey))) // Where the selected coins contains the same script.
                    {
                        coinsToSpend.Add(c);
                    }

                    return(true);
                }
            }

            return(false);
        }
        public async Task CreateNewAsync()
        {
            var config = new WabiSabiConfig {
                MaxInputCountByRound = 1
            };
            var       round = WabiSabiFactory.CreateRound(config);
            var       km    = ServiceFactory.CreateKeyManager("");
            var       key   = BitcoinFactory.CreateHdPubKey(km);
            SmartCoin coin1 = BitcoinFactory.CreateSmartCoin(key, Money.Coins(1m));

            var mockRpc = WabiSabiFactory.CreatePreconfiguredRpcClient(coin1.Coin);

            using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(config, mockRpc, round);

            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromMinutes(1));

            await using var coordinator = new ArenaRequestHandler(config, new Prison(), arena, mockRpc.Object);
            var wabiSabiApi = new WabiSabiController(coordinator);

            var insecureRandom = new InsecureRandom();
            var roundState     = RoundState.FromRound(round);
            var arenaClient    = new ArenaClient(
                roundState.CreateAmountCredentialClient(insecureRandom),
                roundState.CreateVsizeCredentialClient(insecureRandom),
                wabiSabiApi);

            Assert.Equal(Phase.InputRegistration, arena.Rounds.First().Phase);

            var bitcoinSecret = km.GetSecrets("", coin1.ScriptPubKey).Single().PrivateKey.GetBitcoinSecret(Network.Main);

            var aliceClient = new AliceClient(round.Id, arenaClient, coin1.Coin, round.FeeRate, bitcoinSecret);
            await aliceClient.RegisterInputAsync(CancellationToken.None);

            using RoundStateUpdater roundStateUpdater = new(TimeSpan.FromSeconds(2), wabiSabiApi);
            Task confirmationTask = aliceClient.ConfirmConnectionAsync(
                TimeSpan.FromSeconds(1),
                new long[] { coin1.EffectiveValue(round.FeeRate) },
                new long[] { roundState.MaxVsizeAllocationPerAlice - coin1.ScriptPubKey.EstimateInputVsize() },
                roundStateUpdater,
                CancellationToken.None);

            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromMinutes(1));

            await confirmationTask;

            Assert.Equal(Phase.ConnectionConfirmation, arena.Rounds.First().Phase);
        }
Exemple #7
0
 private void RemoveCoin(SmartCoin coinWaitingForMix)
 {
     State.RemoveCoinFromWaitingList(coinWaitingForMix);
     coinWaitingForMix.CoinJoinInProgress = false;
     coinWaitingForMix.Secret             = null;
     if (coinWaitingForMix.Label == "ZeroLink Change" && coinWaitingForMix.Unspent)
     {
         coinWaitingForMix.Label = "ZeroLink Dequeued Change";
         var key = KeyManager.GetKeys(x => x.GetP2wpkhScript() == coinWaitingForMix.ScriptPubKey).SingleOrDefault();
         if (!(key is null))
         {
             key.SetLabel(coinWaitingForMix.Label, KeyManager);
         }
     }
     CoinDequeued?.Invoke(this, coinWaitingForMix);
     Logger.LogInfo <CcjClient>($"Coin dequeued: {coinWaitingForMix.Index}:{coinWaitingForMix.TransactionId}.");
 }
        public async Task CreateNewAsync()
        {
            var config = new WabiSabiConfig {
                MaxInputCountByRound = 1
            };
            var round = WabiSabiFactory.CreateRound(config);

            using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(config, round);

            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromMinutes(1));

            var       km       = ServiceFactory.CreateKeyManager("");
            var       key      = BitcoinFactory.CreateHdPubKey(km);
            SmartCoin coin1    = BitcoinFactory.CreateSmartCoin(key, Money.Coins(1m));
            var       outpoint = coin1.OutPoint;

            var mockRpc = new Mock <IRPCClient>();

            mockRpc.Setup(rpc => rpc.GetTxOutAsync(outpoint.Hash, (int)outpoint.N, true))
            .ReturnsAsync(new NBitcoin.RPC.GetTxOutResponse
            {
                IsCoinBase    = false,
                Confirmations = coin1.Height,
                TxOut         = coin1.TxOut,
            });

            await using var coordinator = new ArenaRequestHandler(config, new Prison(), arena, mockRpc.Object);

            CredentialPool amountCredentialPool = new();
            CredentialPool weightCredentialPool = new();
            var            arenaClient          = new ArenaClient(round.AmountCredentialIssuerParameters, round.WeightCredentialIssuerParameters, amountCredentialPool, weightCredentialPool, coordinator, new InsecureRandom());

            Assert.Equal(Phase.InputRegistration, arena.Rounds.First().Value.Phase);

            var bitcoinSecret = km.GetSecrets("", coin1.ScriptPubKey).Single().PrivateKey.GetBitcoinSecret(Network.Main);
            var aliceClient   = await AliceClient.CreateNewAsync(arenaClient, new[] { coin1.Coin }, bitcoinSecret, round.Id, round.Hash, round.FeeRate);

            Task confirmationTask = aliceClient.ConfirmConnectionAsync(TimeSpan.FromSeconds(3), CancellationToken.None);

            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromMinutes(1));

            await confirmationTask;

            Assert.Equal(Phase.ConnectionConfirmation, arena.Rounds.First().Value.Phase);
        }
Exemple #9
0
        public void ReceiveTransactionForWallet()
        {
            var       transactionProcessor = CreateTransactionProcessor();
            SmartCoin receivedCoin         = null;

            transactionProcessor.CoinReceived += (s, theCoin) => receivedCoin = theCoin;
            var keys = transactionProcessor.KeyManager.GetKeys();
            var tx   = CreateCreditingTransaction(keys.First().P2wpkhScript, Money.Coins(1.0m));

            var relevant = transactionProcessor.Process(tx);

            // It is relevant because is funding the wallet
            Assert.True(relevant);
            var coin = Assert.Single(transactionProcessor.Coins);

            Assert.Equal(Money.Coins(1.0m), coin.Amount);
            Assert.Contains(transactionProcessor.TransactionCache, x => x == tx);
            Assert.NotNull(receivedCoin);
        }
Exemple #10
0
        public static async Task <AliceClient> CreateRegisterAndConfirmInputAsync(
            RoundState roundState,
            ArenaClient arenaClient,
            SmartCoin coin,
            BitcoinSecret bitcoinSecret,
            RoundStateUpdater roundStatusUpdater,
            CancellationToken cancellationToken)
        {
            AliceClient?aliceClient = null;

            try
            {
                aliceClient = await RegisterInputAsync(roundState, arenaClient, coin, bitcoinSecret, cancellationToken).ConfigureAwait(false);

                await aliceClient.ConfirmConnectionAsync(roundStatusUpdater, cancellationToken).ConfigureAwait(false);
            }
            catch (OperationCanceledException)
            {
                if (aliceClient is { })
Exemple #11
0
        public CoinViewModel(SmartCoin model)
        {
            Model = model;

            model.WhenAnyValue(x => x.Confirmed).ObserveOn(RxApp.MainThreadScheduler).Subscribe(confirmed =>
            {
                this.RaisePropertyChanged(nameof(Confirmed));
            });

            model.WhenAnyValue(x => x.SpentOrCoinJoinInProgress).ObserveOn(RxApp.MainThreadScheduler).Subscribe(_ =>
            {
                this.RaisePropertyChanged(nameof(SpentOrCoinJoinInProgress));
            });

            model.WhenAnyValue(x => x.CoinJoinInProgress).ObserveOn(RxApp.MainThreadScheduler).Subscribe(_ =>
            {
                this.RaisePropertyChanged(nameof(CoinJoinInProgress));
            });
        }
    public bool TryAdd(SmartCoin coin)
    {
        var added = false;

        lock (Lock)
        {
            if (!SpentCoins.Contains(coin))
            {
                added = Coins.Add(coin);
                coin.RegisterToHdPubKey();
                if (added)
                {
                    foreach (var outPoint in coin.Transaction.Transaction.Inputs.Select(x => x.PrevOut))
                    {
                        var newCoinSet = new HashSet <SmartCoin> {
                            coin
                        };

                        // If we don't succeed to add a new entry to the dictionary.
                        if (!CoinsByOutPoint.TryAdd(outPoint, newCoinSet))
                        {
                            var previousCoinTxId = CoinsByOutPoint[outPoint].First().TransactionId;

                            // Then check if we're in the same transaction as the previous coins in the dictionary are.
                            if (coin.TransactionId == previousCoinTxId)
                            {
                                // If we are in the same transaction, then just add it to value set.
                                CoinsByOutPoint[outPoint].Add(coin);
                            }
                            else
                            {
                                // If we aren't in the same transaction, then it's a conflict, so replace the old set with the new one.
                                CoinsByOutPoint[outPoint] = newCoinSet;
                            }
                        }
                    }
                    InvalidateSnapshot = true;
                }
            }
        }
        return(added);
    }
        public async Task ReceiveTransactionForWalletAsync()
        {
            var transactionProcessor = await CreateTransactionProcessorAsync();

            SmartCoin receivedCoin = null;

            transactionProcessor.CoinReceived += (s, theCoin) => receivedCoin = theCoin;
            var keys = transactionProcessor.KeyManager.GetKeys();
            var tx   = CreateCreditingTransaction(keys.First().P2wpkhScript, Money.Coins(1.0m));

            var relevant = transactionProcessor.Process(tx);

            // It is relevant because is funding the wallet
            Assert.True(relevant);
            var coin = Assert.Single(transactionProcessor.Coins);

            Assert.Equal(Money.Coins(1.0m), coin.Amount);
            Assert.True(transactionProcessor.TransactionStore.TryGetTransaction(tx.GetHash(), out _));
            Assert.NotNull(receivedCoin);
        }
        private void RemoveCoin(SmartCoin coinWaitingForMix, string reason = null)
        {
            State.RemoveCoinFromWaitingList(coinWaitingForMix);
            coinWaitingForMix.CoinJoinInProgress = false;
            coinWaitingForMix.Secret             = null;
            if (coinWaitingForMix.Label == "ZeroLink Change" && coinWaitingForMix.Unspent)
            {
                coinWaitingForMix.Label = "ZeroLink Dequeued Change";
                var key = KeyManager.GetKeys(x => x.P2wpkhScript == coinWaitingForMix.ScriptPubKey).SingleOrDefault();
                if (key != null)
                {
                    key.SetLabel(coinWaitingForMix.Label, KeyManager);
                }
            }
            CoinDequeued?.Invoke(this, coinWaitingForMix);
            var correctReason = Guard.Correct(reason);
            var reasonText    = correctReason != "" ? $" Reason: {correctReason}" : "";

            Logger.LogInfo <CcjClient>($"Coin dequeued: {coinWaitingForMix.Index}:{coinWaitingForMix.TransactionId}.{reasonText}");
        }
Exemple #15
0
 private AliceClient(
     Guid aliceId,
     RoundState roundState,
     ArenaClient arenaClient,
     SmartCoin coin,
     BitcoinSecret bitcoinSecret,
     IEnumerable <Credential> issuedAmountCredentials,
     IEnumerable <Credential> issuedVsizeCredentials)
 {
     AliceId                    = aliceId;
     RoundId                    = roundState.Id;
     ArenaClient                = arenaClient;
     SmartCoin                  = coin;
     FeeRate                    = roundState.FeeRate;
     BitcoinSecret              = bitcoinSecret;
     IssuedAmountCredentials    = issuedAmountCredentials;
     IssuedVsizeCredentials     = issuedVsizeCredentials;
     MaxVsizeAllocationPerAlice = roundState.MaxVsizeAllocationPerAlice;
     ConfirmationTimeout        = roundState.ConnectionConfirmationTimeout / 2;
 }
Exemple #16
0
    public static async Task <AliceClient> CreateRegisterAndConfirmInputAsync(
        RoundState roundState,
        ArenaClient arenaClient,
        SmartCoin coin,
        IKeyChain keyChain,
        RoundStateUpdater roundStatusUpdater,
        CancellationToken cancellationToken)
    {
        AliceClient?aliceClient = null;

        try
        {
            aliceClient = await RegisterInputAsync(roundState, arenaClient, coin, keyChain, cancellationToken).ConfigureAwait(false);

            await aliceClient.ConfirmConnectionAsync(roundStatusUpdater, cancellationToken).ConfigureAwait(false);

            Logger.LogInfo($"Round ({aliceClient.RoundId}), Alice ({aliceClient.AliceId}): Connection was confirmed.");
        }
        catch (OperationCanceledException)
        {
            if (aliceClient is { })
    public void OnlyOneNonPrivateCoinInBigSetOfCoinsConsolidationMode()
    {
        const int MinAnonimitySet = 10;
        var       km = KeyManager.CreateNew(out _, "", Network.Main);
        SmartCoin smallerAnonCoin   = BitcoinFactory.CreateSmartCoin(BitcoinFactory.CreateHdPubKey(km), Money.Coins(1m), 0, anonymitySet: MinAnonimitySet - 1);
        var       coinsToSelectFrom = Enumerable
                                      .Range(0, 10)
                                      .Select(i => BitcoinFactory.CreateSmartCoin(BitcoinFactory.CreateHdPubKey(km), Money.Coins(1m), 0, anonymitySet: MinAnonimitySet + 1))
                                      .Prepend(smallerAnonCoin)
                                      .ToList();

        var coins = CoinJoinClient.SelectCoinsForRound(
            coins: coinsToSelectFrom,
            CreateMultipartyTransactionParameters(),
            consolidationMode: true,
            minAnonScoreTarget: MinAnonimitySet,
            ConfigureRng(5));

        Assert.Contains(smallerAnonCoin, coins);
        Assert.Equal(10, coins.Count);
    }
        CreateRoundWithTwoConfirmedConnectionsAsync(Arena arena, Key key1, SmartCoin coin1, Key key2, SmartCoin coin2)
        {
            // Create the round.
            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

            var arenaClient = WabiSabiFactory.CreateArenaClient(arena);
            var round       = Assert.Single(arena.Rounds);

            round.MaxVsizeAllocationPerAlice = 11 + 31 + MultipartyTransactionParameters.SharedOverhead;

            using RoundStateUpdater roundStateUpdater = new(TimeSpan.FromSeconds(2), arena);
            await roundStateUpdater.StartAsync(CancellationToken.None);

            var identificationKey = new Key();
            var task1             = AliceClient.CreateRegisterAndConfirmInputAsync(RoundState.FromRound(round), arenaClient, coin1, key1.GetBitcoinSecret(round.Network), identificationKey, roundStateUpdater, CancellationToken.None);
            var task2             = AliceClient.CreateRegisterAndConfirmInputAsync(RoundState.FromRound(round), arenaClient, coin2, key2.GetBitcoinSecret(round.Network), identificationKey, roundStateUpdater, CancellationToken.None);

            while (Phase.ConnectionConfirmation != round.Phase)
            {
                await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));
            }
            await Task.WhenAll(task1, task2);

            var aliceClient1 = task1.Result;
            var aliceClient2 = task2.Result;

            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

            Assert.Equal(Phase.OutputRegistration, round.Phase);

            await roundStateUpdater.StopAsync(CancellationToken.None);

            return(round,
                   arenaClient,
                   new[]
            {
                aliceClient1,
                aliceClient2
            });
        }
    CreateRoundWithTwoConfirmedConnectionsAsync(Arena arena, IKeyChain keyChain, SmartCoin coin1, SmartCoin coin2)
    {
        // Get the round.
        await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

        var arenaClient = WabiSabiFactory.CreateArenaClient(arena);
        var round       = Assert.Single(arena.Rounds);

        // Refresh the Arena States because of vsize manipulation.
        await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

        using RoundStateUpdater roundStateUpdater = new(TimeSpan.FromSeconds(2), arena);
        await roundStateUpdater.StartAsync(CancellationToken.None);

        var task1 = AliceClient.CreateRegisterAndConfirmInputAsync(RoundState.FromRound(round), arenaClient, coin1, keyChain, roundStateUpdater, CancellationToken.None);
        var task2 = AliceClient.CreateRegisterAndConfirmInputAsync(RoundState.FromRound(round), arenaClient, coin2, keyChain, roundStateUpdater, CancellationToken.None);

        while (Phase.ConnectionConfirmation != round.Phase)
        {
            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));
        }
        await Task.WhenAll(task1, task2);

        var aliceClient1 = await task1;
        var aliceClient2 = await task2;

        await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

        Assert.Equal(Phase.OutputRegistration, round.Phase);

        await roundStateUpdater.StopAsync(CancellationToken.None);

        return(round,
               arenaClient,
               new[]
        {
            aliceClient1,
            aliceClient2
        });
    }
Exemple #20
0
 private AliceClient(
     Guid aliceId,
     RoundState roundState,
     ArenaClient arenaClient,
     SmartCoin coin,
     OwnershipProof ownershipProof,
     IEnumerable <Credential> issuedAmountCredentials,
     IEnumerable <Credential> issuedVsizeCredentials,
     bool isPayingZeroCoordinationFee)
 {
     AliceId                     = aliceId;
     RoundId                     = roundState.Id;
     ArenaClient                 = arenaClient;
     SmartCoin                   = coin;
     OwnershipProof              = ownershipProof;
     FeeRate                     = roundState.FeeRate;
     CoordinationFeeRate         = roundState.CoordinationFeeRate;
     IssuedAmountCredentials     = issuedAmountCredentials;
     IssuedVsizeCredentials      = issuedVsizeCredentials;
     MaxVsizeAllocationPerAlice  = roundState.MaxVsizeAllocationPerAlice;
     ConfirmationTimeout         = roundState.ConnectionConfirmationTimeout / 2;
     IsPayingZeroCoordinationFee = isPayingZeroCoordinationFee;
 }
        private async Task BroadcastTransactionToBackendAsync(SmartTransaction transaction)
        {
            Logger.LogInfo("Broadcasting with backend...");
            using (var client = new WasabiClient(Synchronizer.WasabiClient.TorClient.DestinationUriAction, Synchronizer.WasabiClient.TorClient.TorSocks5EndPoint))
            {
                try
                {
                    await client.BroadcastAsync(transaction).ConfigureAwait(false);
                }
                catch (HttpRequestException ex2) when(ex2.Message.Contains("bad-txns-inputs-missingorspent", StringComparison.InvariantCultureIgnoreCase) ||
                                                      ex2.Message.Contains("missing-inputs", StringComparison.InvariantCultureIgnoreCase) ||
                                                      ex2.Message.Contains("txn-mempool-conflict", StringComparison.InvariantCultureIgnoreCase))
                {
                    if (transaction.Transaction.Inputs.Count == 1)                     // If we tried to only spend one coin, then we can mark it as spent. If there were more coins, then we do not know.
                    {
                        OutPoint input = transaction.Transaction.Inputs.First().PrevOut;
                        lock (WalletServicesLock)
                        {
                            foreach (var walletService in AliveWalletServices)
                            {
                                SmartCoin coin = walletService.Coins.GetByOutPoint(input);
                                if (coin != default)
                                {
                                    coin.SpentAccordingToBackend = true;
                                }
                            }
                        }
                    }

                    throw;
                }
            }

            BelieveTransaction(transaction);

            Logger.LogInfo($"Transaction is successfully broadcasted to backend: {transaction.GetHash()}.");
        }
Exemple #22
0
        public void SmartCoinSerialization()
        {
            var tx           = Transaction.Parse("02000000040aa8d0af84518df6e3a60c5bb19d9c3fcc3dc6e26b2f2449e8d7bf8d3fe84b87010000006a473044022018dfe9216c1209dd6c2b6c1607dbac4e499c1fce4878bc7d5d83fccbf3e24c9402202cac351c9c6a2b5eef338cbf0ec000d8de1c05e96a904cbba2b9e6ffc2d4e19501210364cc39da1091b1a9c12ec905a14a9e8478f951f7a1accdabeb40180533f2eaa5feffffff112c07d0f5e0617d720534f0b2b84dc0d5b7314b358c3ab338823b9e5bfbddf5010000006b483045022100ec155e7141e74661ee511ae980150a6c89261f31070999858738369afc28f2b6022006230d2aa24fac110b74ef15b84371486cf76c539b335a253c14462447912a300121020c2f41390f031d471b22abdb856e6cdbe0f4d74e72c197469bfd54e5a08f7e67feffffff38e799b8f6cf04fd021a9b135cdcd347da7aac4fd8bb8d0da9316a9fb228bb6e000000006b483045022100fc1944544a3f96edd8c8a9795c691e2725612b5ab2e1c999be11a2a4e3f841f1022077b2e088877829edeada0c707a9bb577aa79f26dafacba3d1d2d047f52524296012102e6015963dff9826836400cf8f45597c0705757d5dcdc6bf734f661c7dab89e69feffffff64c3f0377e86625123f2f1ee229319ed238e8ca8b7dda5bc080a2c5ecb984629000000006a47304402204233a90d6296182914424fd2901e16e6f5b13b451b67b0eec25a5eaacc5033c902203d8a13ef0b494c12009663475458e51da6bd55cc67688264230ece81d3eeca24012102f806d7152da2b52c1d9ad928e4a6253ccba080a5b9ab9efdd80e37274ac67f9bfeffffff0290406900000000001976a91491ac4e49b66f845180d98d8f8be6121588be6e3b88ac52371600000000001976a9142f44ed6749e8c84fd476e4440741f7e6f55542fa88acadd30700", Network.TestNet);
            var txId         = tx.GetHash();
            var index        = 0U;
            var output       = tx.Outputs[0];
            var scriptPubKey = output.ScriptPubKey;
            var amount       = output.Value;
            var spentOutputs = tx.Inputs.ToTxoRefs().ToArray();
            var height       = Height.MemPool;
            var label        = "foo";
            var bannedUntil  = DateTimeOffset.UtcNow;

            var coin = new SmartCoin(txId, index, scriptPubKey, amount, spentOutputs, height, tx.RBF, tx.GetMixin(index), label, txId);

            coin.BannedUntilUtc = bannedUntil;

            var serialized   = JsonConvert.SerializeObject(coin);
            var deserialized = JsonConvert.DeserializeObject <SmartCoin>(serialized);

            Assert.Equal(coin, deserialized);
            Assert.Equal(coin.Height, deserialized.Height);
            Assert.Equal(coin.Amount, deserialized.Amount);
            Assert.Equal(coin.Index, deserialized.Index);
            Assert.Equal(coin.SpentOrCoinJoinInProgress, deserialized.SpentOrCoinJoinInProgress);
            Assert.Equal(coin.Label, deserialized.Label);
            Assert.Equal(coin.ScriptPubKey, deserialized.ScriptPubKey);
            Assert.Equal(coin.SpenderTransactionId, deserialized.SpenderTransactionId);
            Assert.Equal(coin.TransactionId, deserialized.TransactionId);
            Assert.Equal(coin.SpentOutputs.Length, deserialized.SpentOutputs.Length);
            Assert.Equal(coin.BannedUntilUtc, deserialized.BannedUntilUtc);
            for (int i = 0; i < coin.SpentOutputs.Length; i++)
            {
                Assert.Equal(coin.SpentOutputs[0], deserialized.SpentOutputs[0]);
            }
        }
Exemple #23
0
        public void SpendCoin()
        {
            var       transactionProcessor = CreateTransactionProcessor();
            SmartCoin spentCoin            = null;

            transactionProcessor.CoinSpent += (s, theCoin) => spentCoin = theCoin;
            var keys = transactionProcessor.KeyManager.GetKeys();
            var tx   = CreateCreditingTransaction(keys.First().P2wpkhScript, Money.Coins(1.0m));

            transactionProcessor.Process(tx);

            var createdCoin = tx.Transaction.Outputs.AsCoins().First();

            // Spend the received coin
            tx = CreateSpendingTransaction(createdCoin, new Key().PubKey.ScriptPubKey);
            var relevant = transactionProcessor.Process(tx);

            Assert.True(relevant);
            var coin = Assert.Single(transactionProcessor.Coins);

            Assert.False(coin.Unspent);
            Assert.NotNull(spentCoin);
            Assert.Equal(coin, spentCoin);
        }
        public CoinViewModel(CoinListViewModel owner, SmartCoin model)
        {
            _model = model;

            this.WhenAnyValue(x => x.IsSelected).Subscribe(selected =>
            {
                if (selected)
                {
                    if (!owner.SelectedCoins.Contains(this))
                    {
                        owner.SelectedCoins.Add(this);
                    }
                }
                else
                {
                    owner.SelectedCoins.Remove(this);
                }
            });

            model.WhenAnyValue(x => x.Confirmed).ObserveOn(RxApp.MainThreadScheduler).Subscribe(confirmed =>
            {
                this.RaisePropertyChanged(nameof(Confirmed));
            });
        }
Exemple #25
0
 public CoinViewModel(CoinListViewModel owner, SmartCoin model)
 {
     Model  = model;
     _owner = owner;
 }
Exemple #26
0
        public bool Process(SmartTransaction tx)
        {
            if (!tx.Transaction.PossiblyP2WPKHInvolved())
            {
                return(false);                // We do not care about non-witness transactions for other than mempool cleanup.
            }

            uint256 txId           = tx.GetHash();
            var     walletRelevant = false;

            if (tx.Confirmed)
            {
                foreach (var coin in Coins.Where(x => x.TransactionId == txId))
                {
                    coin.Height    = tx.Height;
                    walletRelevant = true;                     // relevant
                }
            }

            if (!tx.Transaction.IsCoinBase)             // Transactions we already have and processed would be "double spends" but they shouldn't.
            {
                var doubleSpends = new List <SmartCoin>();
                foreach (SmartCoin coin in Coins)
                {
                    var spent = false;
                    foreach (TxoRef spentOutput in coin.SpentOutputs)
                    {
                        foreach (TxIn txIn in tx.Transaction.Inputs)
                        {
                            if (spentOutput.TransactionId == txIn.PrevOut.Hash && spentOutput.Index == txIn.PrevOut.N)                             // Do not do (spentOutput == txIn.PrevOut), it's faster this way, because it won't check for null.
                            {
                                doubleSpends.Add(coin);
                                spent          = true;
                                walletRelevant = true;
                                break;
                            }
                        }
                        if (spent)
                        {
                            break;
                        }
                    }
                }

                if (doubleSpends.Any())
                {
                    if (tx.Height == Height.Mempool)
                    {
                        // if the received transaction is spending at least one input already
                        // spent by a previous unconfirmed transaction signaling RBF then it is not a double
                        // spanding transaction but a replacement transaction.
                        if (doubleSpends.Any(x => x.IsReplaceable))
                        {
                            // remove double spent coins (if other coin spends it, remove that too and so on)
                            // will add later if they came to our keys
                            foreach (SmartCoin doubleSpentCoin in doubleSpends.Where(x => !x.Confirmed))
                            {
                                Coins.TryRemove(doubleSpentCoin);
                            }
                            tx.SetReplacement();
                            walletRelevant = true;
                        }
                        else
                        {
                            return(false);
                        }
                    }
                    else                     // new confirmation always enjoys priority
                    {
                        // remove double spent coins recursively (if other coin spends it, remove that too and so on), will add later if they came to our keys
                        foreach (SmartCoin doubleSpentCoin in doubleSpends)
                        {
                            Coins.TryRemove(doubleSpentCoin);
                        }
                        walletRelevant = true;
                    }
                }
            }

            var  isLikelyCoinJoinOutput = false;
            bool hasEqualOutputs        = tx.Transaction.GetIndistinguishableOutputs(includeSingle: false).FirstOrDefault() != default;

            if (hasEqualOutputs)
            {
                var  receiveKeys         = KeyManager.GetKeys(x => tx.Transaction.Outputs.Any(y => y.ScriptPubKey == x.P2wpkhScript));
                bool allReceivedInternal = receiveKeys.All(x => x.IsInternal);
                if (allReceivedInternal)
                {
                    // It is likely a coinjoin if the diff between receive and sent amount is small and have at least 2 equal outputs.
                    Money spentAmount    = Coins.Where(x => tx.Transaction.Inputs.Any(y => y.PrevOut.Hash == x.TransactionId && y.PrevOut.N == x.Index)).Sum(x => x.Amount);
                    Money receivedAmount = tx.Transaction.Outputs.Where(x => receiveKeys.Any(y => y.P2wpkhScript == x.ScriptPubKey)).Sum(x => x.Value);
                    bool  receivedAlmostAsMuchAsSpent = spentAmount.Almost(receivedAmount, Money.Coins(0.005m));

                    if (receivedAlmostAsMuchAsSpent)
                    {
                        isLikelyCoinJoinOutput = true;
                    }
                }
            }

            List <SmartCoin> spentOwnCoins = null;

            for (var i = 0U; i < tx.Transaction.Outputs.Count; i++)
            {
                // If transaction received to any of the wallet keys:
                var      output   = tx.Transaction.Outputs[i];
                HdPubKey foundKey = KeyManager.GetKeyForScriptPubKey(output.ScriptPubKey);
                if (foundKey != default)
                {
                    walletRelevant = true;

                    if (output.Value <= DustThreshold)
                    {
                        continue;
                    }

                    foundKey.SetKeyState(KeyState.Used, KeyManager);
                    spentOwnCoins ??= Coins.Where(x => tx.Transaction.Inputs.Any(y => y.PrevOut.Hash == x.TransactionId && y.PrevOut.N == x.Index)).ToList();
                    var anonset = tx.Transaction.GetAnonymitySet(i);
                    if (spentOwnCoins.Count != 0)
                    {
                        anonset += spentOwnCoins.Min(x => x.AnonymitySet) - 1;                         // Minus 1, because do not count own.
                    }

                    SmartCoin newCoin = new SmartCoin(txId, i, output.ScriptPubKey, output.Value, tx.Transaction.Inputs.ToTxoRefs().ToArray(), tx.Height, tx.IsRBF, anonset, isLikelyCoinJoinOutput, foundKey.Label, spenderTransactionId: null, false, pubKey: foundKey);                     // Do not inherit locked status from key, that's different.
                    // If we did not have it.
                    if (Coins.TryAdd(newCoin))
                    {
                        CoinReceived?.Invoke(this, newCoin);

                        // Make sure there's always 21 clean keys generated and indexed.
                        KeyManager.AssertCleanKeysIndexed(isInternal: foundKey.IsInternal);

                        if (foundKey.IsInternal)
                        {
                            // Make sure there's always 14 internal locked keys generated and indexed.
                            KeyManager.AssertLockedInternalKeysIndexed(14);
                        }
                    }
                    else                                      // If we had this coin already.
                    {
                        if (newCoin.Height != Height.Mempool) // Update the height of this old coin we already had.
                        {
                            SmartCoin oldCoin = Coins.FirstOrDefault(x => x.TransactionId == txId && x.Index == i);
                            if (oldCoin != null)                             // Just to be sure, it is a concurrent collection.
                            {
                                oldCoin.Height = newCoin.Height;
                            }
                        }
                    }
                }
            }

            // If spends any of our coin
            for (var i = 0; i < tx.Transaction.Inputs.Count; i++)
            {
                var input = tx.Transaction.Inputs[i];

                var foundCoin = Coins.FirstOrDefault(x => x.TransactionId == input.PrevOut.Hash && x.Index == input.PrevOut.N);
                if (foundCoin != null)
                {
                    walletRelevant = true;
                    var alreadyKnown = foundCoin.SpenderTransactionId == txId;
                    foundCoin.SpenderTransactionId = txId;

                    if (!alreadyKnown)
                    {
                        CoinSpent?.Invoke(this, foundCoin);
                    }

                    if (tx.Confirmed)
                    {
                        SpenderConfirmed?.Invoke(this, foundCoin);
                    }
                }
            }

            if (walletRelevant)
            {
                TransactionStore.AddOrUpdate(tx);
            }

            return(walletRelevant);
        }
Exemple #27
0
        private async Task TryRegisterCoinsAsync(CcjClientRound inputRegistrableRound)
        {
            try
            {
                List <(uint256 txid, uint index)> registrableCoins = State.GetRegistrableCoins(
                    inputRegistrableRound.State.MaximumInputCountPerPeer,
                    inputRegistrableRound.State.Denomination,
                    inputRegistrableRound.State.FeePerInputs,
                    inputRegistrableRound.State.FeePerOutputs).ToList();

                if (registrableCoins.Any())
                {
                    BitcoinAddress changeAddress = null;
                    BitcoinAddress activeAddress = null;
                    lock (CustomChangeAddressesLock)
                    {
                        if (CustomChangeAddresses.Count > 0)
                        {
                            changeAddress = CustomChangeAddresses.First();
                            CustomChangeAddresses.RemoveFirst();
                        }
                    }
                    lock (CustomActiveAddressesLock)
                    {
                        if (CustomActiveAddresses.Count > 0)
                        {
                            activeAddress = CustomActiveAddresses.First();
                            CustomActiveAddresses.RemoveFirst();
                        }
                    }

                    if (changeAddress is null || activeAddress is null)
                    {
                        IEnumerable <HdPubKey> allUnusedInternalKeys = KeyManager.GetKeys(keyState: null, isInternal: true).Where(x => x.KeyState != KeyState.Used);

                        if (changeAddress is null)
                        {
                            string changeLabel = "ZeroLink Change";
                            IEnumerable <HdPubKey> allChangeKeys = allUnusedInternalKeys.Where(x => x.Label == changeLabel);
                            HdPubKey changeKey = null;

                            KeyManager.AssertLockedInternalKeysIndexed(14);
                            IEnumerable <HdPubKey> internalNotCachedLockedKeys = KeyManager.GetKeys(KeyState.Locked, isInternal: true).Except(AccessCache.Keys);

                            if (allChangeKeys.Count() >= 7 || !internalNotCachedLockedKeys.Any())                             // Then don't generate new keys, because it'd bloat the wallet.
                            {
                                // Find the first one that we did not try to register in the current session.
                                changeKey = allChangeKeys.FirstOrDefault(x => !AccessCache.ContainsKey(x));
                                // If there is no such a key, then use the oldest.
                                if (changeKey == default)
                                {
                                    changeKey = AccessCache.Where(x => allChangeKeys.Contains(x.Key)).OrderBy(x => x.Value).First().Key;
                                }
                                changeKey.SetLabel(changeLabel);
                                changeKey.SetKeyState(KeyState.Locked);
                            }
                            else
                            {
                                changeKey = internalNotCachedLockedKeys.RandomElement();
                                changeKey.SetLabel(changeLabel);
                            }
                            changeAddress = changeKey.GetP2wpkhAddress(Network);
                            AccessCache.AddOrReplace(changeKey, DateTimeOffset.UtcNow);
                        }

                        if (activeAddress is null)
                        {
                            string activeLabel = "ZeroLink Mixed Coin";
                            IEnumerable <HdPubKey> allActiveKeys = allUnusedInternalKeys.Where(x => x.Label == activeLabel);
                            HdPubKey activeKey = null;

                            KeyManager.AssertLockedInternalKeysIndexed(14);
                            IEnumerable <HdPubKey> internalNotCachedLockedKeys = KeyManager.GetKeys(KeyState.Locked, isInternal: true).Except(AccessCache.Keys);

                            if (allActiveKeys.Count() >= 7 || !internalNotCachedLockedKeys.Any())                             // Then don't generate new keys, because it'd bloat the wallet.
                            {
                                // Find the first one that we did not try to register in the current session.
                                activeKey = allActiveKeys.FirstOrDefault(x => !AccessCache.ContainsKey(x));
                                // If there is no such a key, then use the oldest.
                                if (activeKey == default)
                                {
                                    activeKey = AccessCache.Where(x => allActiveKeys.Contains(x.Key)).OrderBy(x => x.Value).First().Key;
                                }
                                activeKey.SetLabel(activeLabel);
                                activeKey.SetKeyState(KeyState.Locked);
                                activeAddress = activeKey.GetP2wpkhAddress(Network);
                            }
                            else
                            {
                                activeKey = internalNotCachedLockedKeys.RandomElement();
                                activeKey.SetLabel(activeLabel);
                            }
                            activeAddress = activeKey.GetP2wpkhAddress(Network);
                            AccessCache.AddOrReplace(activeKey, DateTimeOffset.UtcNow);
                        }
                    }

                    KeyManager.ToFile();

                    var blind = CoordinatorPubKey.Blind(activeAddress.ScriptPubKey.ToBytes());

                    var inputProofs = new List <InputProofModel>();
                    foreach ((uint256 txid, uint index)coinReference in registrableCoins)
                    {
                        SmartCoin coin = State.GetSingleOrDefaultFromWaitingList(coinReference);
                        if (coin is null)
                        {
                            throw new NotSupportedException("This is impossible.");
                        }
                        coin.Secret = coin.Secret ?? KeyManager.GetSecrets(OnePiece, coin.ScriptPubKey).Single();
                        var inputProof = new InputProofModel
                        {
                            Input = coin.GetTxoRef(),
                            Proof = coin.Secret.PrivateKey.SignMessage(ByteHelpers.ToHex(blind.BlindedData))
                        };
                        inputProofs.Add(inputProof);
                    }
                    AliceClient aliceClient = await AliceClient.CreateNewAsync(Network, changeAddress, blind.BlindedData, inputProofs, CcjHostUri, TorSocks5EndPoint);

                    byte[] unblindedSignature = CoordinatorPubKey.UnblindSignature(aliceClient.BlindedOutputSignature, blind.BlindingFactor);

                    if (!CoordinatorPubKey.Verify(unblindedSignature, activeAddress.ScriptPubKey.ToBytes()))
                    {
                        throw new NotSupportedException("Coordinator did not sign the blinded output properly.");
                    }

                    CcjClientRound roundRegistered = State.GetSingleOrDefaultRound(aliceClient.RoundId);
                    if (roundRegistered is 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, aliceClient.RoundId, registeredPeerCount: 1));
                        State.AddOrReplaceRound(roundRegistered);
                    }

                    foreach ((uint256 txid, uint index)coinReference in registrableCoins)
                    {
                        var coin = State.GetSingleOrDefaultFromWaitingList(coinReference);
                        if (coin is null)
                        {
                            throw new NotSupportedException("This is impossible.");
                        }
                        roundRegistered.CoinsRegistered.Add(coin);
                        State.RemoveCoinFromWaitingList(coin);
                    }
                    roundRegistered.ActiveOutputAddress = activeAddress;
                    roundRegistered.ChangeOutputAddress = changeAddress;
                    roundRegistered.UnblindedSignature  = unblindedSignature;
                    roundRegistered.AliceClient         = aliceClient;
                }
            }
            catch (Exception ex)
            {
                Logger.LogError <CcjClient>(ex);
            }
        }
        public async Task SendTestsAsync()
        {
            (string password, IRPCClient rpc, Network network, _, ServiceConfiguration serviceConfiguration, BitcoinStore bitcoinStore, Backend.Global global) = await Common.InitializeTestEnvironmentAsync(RegTestFixture, 1);

            bitcoinStore.IndexStore.NewFilter += Common.Wallet_NewFilterProcessed;
            // Create the services.
            // 1. Create connection service.
            var nodes = new NodesGroup(global.Config.Network, requirements: Constants.NodeRequirements);

            nodes.ConnectedNodes.Add(await RegTestFixture.BackendRegTestNode.CreateNewP2pNodeAsync());

            // 2. Create mempool service.

            Node node = await RegTestFixture.BackendRegTestNode.CreateNewP2pNodeAsync();

            node.Behaviors.Add(bitcoinStore.CreateUntrustedP2pBehavior());

            // 3. Create wasabi synchronizer service.
            var synchronizer = new WasabiSynchronizer(rpc.Network, bitcoinStore, new Uri(RegTestFixture.BackendEndPoint), null);

            // 4. Create key manager service.
            var keyManager = KeyManager.CreateNew(out _, password);

            // 5. Create wallet service.
            var workDir = Tests.Common.GetWorkDir();

            CachedBlockProvider blockProvider = new CachedBlockProvider(
                new P2pBlockProvider(nodes, null, synchronizer, serviceConfiguration, network),
                bitcoinStore.BlockRepository);

            var walletManager = new WalletManager(network, new WalletDirectories(workDir));

            walletManager.RegisterServices(bitcoinStore, synchronizer, nodes, serviceConfiguration, synchronizer, blockProvider);

            // Get some money, make it confirm.
            var key  = keyManager.GetNextReceiveKey("foo label", out _);
            var key2 = keyManager.GetNextReceiveKey("foo label", out _);
            var txId = await rpc.SendToAddressAsync(key.GetP2wpkhAddress(network), Money.Coins(1m));

            Assert.NotNull(txId);
            await rpc.GenerateAsync(1);

            var txId2 = await rpc.SendToAddressAsync(key2.GetP2wpkhAddress(network), Money.Coins(1m));

            Assert.NotNull(txId2);
            await rpc.GenerateAsync(1);

            try
            {
                Interlocked.Exchange(ref Common.FiltersProcessedByWalletCount, 0);
                nodes.Connect();                                                                              // Start connection service.
                node.VersionHandshake();                                                                      // Start mempool service.
                synchronizer.Start(requestInterval: TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(5), 10000); // Start wasabi synchronizer service.

                // Wait until the filter our previous transaction is present.
                var blockCount = await rpc.GetBlockCountAsync();

                await Common.WaitForFiltersToBeProcessedAsync(TimeSpan.FromSeconds(120), blockCount);

                var wallet = await walletManager.AddAndStartWalletAsync(keyManager);

                var broadcaster = new TransactionBroadcaster(network, bitcoinStore, synchronizer, nodes, walletManager, rpc);

                var waitCount = 0;
                while (wallet.Coins.Sum(x => x.Amount) == Money.Zero)
                {
                    await Task.Delay(1000);

                    waitCount++;
                    if (waitCount >= 21)
                    {
                        Logger.LogInfo("Funding transaction to the wallet did not arrive.");
                        return;                         // Very rarely this test fails. I have no clue why. Probably because all these RegTests are interconnected, anyway let's not bother the CI with it.
                    }
                }

                var scp  = new Key().ScriptPubKey;
                var res2 = wallet.BuildTransaction(password, new PaymentIntent(scp, Money.Coins(0.05m), label: "foo"), FeeStrategy.CreateFromConfirmationTarget(5), allowUnconfirmed: false);

                Assert.NotNull(res2.Transaction);
                Assert.Single(res2.OuterWalletOutputs);
                Assert.Equal(scp, res2.OuterWalletOutputs.Single().ScriptPubKey);
                Assert.Single(res2.InnerWalletOutputs);
                Assert.True(res2.Fee > Money.Satoshis(2 * 100));                 // since there is a sanity check of 2sat/vb in the server
                Assert.InRange(res2.FeePercentOfSent, 0, 1);
                Assert.Single(res2.SpentCoins);
                var spentCoin = Assert.Single(res2.SpentCoins);
                Assert.Contains(new[] { key.P2wpkhScript, key2.P2wpkhScript }, x => x == spentCoin.ScriptPubKey);
                Assert.Equal(Money.Coins(1m), res2.SpentCoins.Single().Amount);
                Assert.False(res2.SpendsUnconfirmed);

                await broadcaster.SendTransactionAsync(res2.Transaction);

                Assert.Contains(res2.InnerWalletOutputs.Single(), wallet.Coins);

                #region Basic

                Script receive      = keyManager.GetNextReceiveKey("Basic", out _).P2wpkhScript;
                Money  amountToSend = wallet.Coins.Where(x => !x.Unavailable).Sum(x => x.Amount) / 2;
                var    res          = wallet.BuildTransaction(password, new PaymentIntent(receive, amountToSend, label: "foo"), FeeStrategy.SevenDaysConfirmationTargetStrategy, allowUnconfirmed: true);

                foreach (SmartCoin coin in res.SpentCoins)
                {
                    Assert.False(coin.CoinJoinInProgress);
                    Assert.True(coin.Confirmed);
                    Assert.Null(coin.SpenderTransactionId);
                    Assert.True(coin.Unspent);
                }

                Assert.Equal(2, res.InnerWalletOutputs.Count());
                Assert.Empty(res.OuterWalletOutputs);
                var activeOutput = res.InnerWalletOutputs.Single(x => x.ScriptPubKey == receive);
                var changeOutput = res.InnerWalletOutputs.Single(x => x.ScriptPubKey != receive);

                Assert.Equal(receive, activeOutput.ScriptPubKey);
                Assert.Equal(amountToSend, activeOutput.Amount);
                if (res.SpentCoins.Sum(x => x.Amount) - activeOutput.Amount == res.Fee)                 // this happens when change is too small
                {
                    Assert.Contains(res.Transaction.Transaction.Outputs, x => x.Value == activeOutput.Amount);
                    Logger.LogInfo($"Change Output: {changeOutput.Amount.ToString(false, true)} {changeOutput.ScriptPubKey.GetDestinationAddress(network)}");
                }
                Logger.LogInfo($"{nameof(res.Fee)}: {res.Fee}");
                Logger.LogInfo($"{nameof(res.FeePercentOfSent)}: {res.FeePercentOfSent} %");
                Logger.LogInfo($"{nameof(res.SpendsUnconfirmed)}: {res.SpendsUnconfirmed}");
                Logger.LogInfo($"Active Output: {activeOutput.Amount.ToString(false, true)} {activeOutput.ScriptPubKey.GetDestinationAddress(network)}");
                Logger.LogInfo($"TxId: {res.Transaction.GetHash()}");

                var foundReceive = false;
                Assert.InRange(res.Transaction.Transaction.Outputs.Count, 1, 2);
                foreach (var output in res.Transaction.Transaction.Outputs)
                {
                    if (output.ScriptPubKey == receive)
                    {
                        foundReceive = true;
                        Assert.Equal(amountToSend, output.Value);
                    }
                }
                Assert.True(foundReceive);

                await broadcaster.SendTransactionAsync(res.Transaction);

                #endregion Basic

                #region SubtractFeeFromAmount

                receive      = keyManager.GetNextReceiveKey("SubtractFeeFromAmount", out _).P2wpkhScript;
                amountToSend = wallet.Coins.Where(x => !x.Unavailable).Sum(x => x.Amount) / 3;
                res          = wallet.BuildTransaction(password, new PaymentIntent(receive, amountToSend, subtractFee: true, label: "foo"), FeeStrategy.SevenDaysConfirmationTargetStrategy, allowUnconfirmed: true);

                Assert.Equal(2, res.InnerWalletOutputs.Count());
                Assert.Empty(res.OuterWalletOutputs);
                activeOutput = res.InnerWalletOutputs.Single(x => x.ScriptPubKey == receive);
                changeOutput = res.InnerWalletOutputs.Single(x => x.ScriptPubKey != receive);

                Assert.Equal(receive, activeOutput.ScriptPubKey);
                Assert.Equal(amountToSend - res.Fee, activeOutput.Amount);
                Assert.Contains(res.Transaction.Transaction.Outputs, x => x.Value == changeOutput.Amount);
                Logger.LogInfo($"{nameof(res.Fee)}: {res.Fee}");
                Logger.LogInfo($"{nameof(res.FeePercentOfSent)}: {res.FeePercentOfSent} %");
                Logger.LogInfo($"{nameof(res.SpendsUnconfirmed)}: {res.SpendsUnconfirmed}");
                Logger.LogInfo($"Active Output: {activeOutput.Amount.ToString(false, true)} {activeOutput.ScriptPubKey.GetDestinationAddress(network)}");
                Logger.LogInfo($"Change Output: {changeOutput.Amount.ToString(false, true)} {changeOutput.ScriptPubKey.GetDestinationAddress(network)}");
                Logger.LogInfo($"TxId: {res.Transaction.GetHash()}");

                foundReceive = false;
                Assert.InRange(res.Transaction.Transaction.Outputs.Count, 1, 2);
                foreach (var output in res.Transaction.Transaction.Outputs)
                {
                    if (output.ScriptPubKey == receive)
                    {
                        foundReceive = true;
                        Assert.Equal(amountToSend - res.Fee, output.Value);
                    }
                }
                Assert.True(foundReceive);

                #endregion SubtractFeeFromAmount

                #region LowFee

                res = wallet.BuildTransaction(password, new PaymentIntent(receive, amountToSend, label: "foo"), FeeStrategy.SevenDaysConfirmationTargetStrategy, allowUnconfirmed: true);

                Assert.Equal(2, res.InnerWalletOutputs.Count());
                Assert.Empty(res.OuterWalletOutputs);
                activeOutput = res.InnerWalletOutputs.Single(x => x.ScriptPubKey == receive);
                changeOutput = res.InnerWalletOutputs.Single(x => x.ScriptPubKey != receive);

                Assert.Equal(receive, activeOutput.ScriptPubKey);
                Assert.Equal(amountToSend, activeOutput.Amount);
                Assert.Contains(res.Transaction.Transaction.Outputs, x => x.Value == changeOutput.Amount);
                Logger.LogInfo($"{nameof(res.Fee)}: {res.Fee}");
                Logger.LogInfo($"{nameof(res.FeePercentOfSent)}: {res.FeePercentOfSent} %");
                Logger.LogInfo($"{nameof(res.SpendsUnconfirmed)}: {res.SpendsUnconfirmed}");
                Logger.LogInfo($"Active Output: {activeOutput.Amount.ToString(false, true)} {activeOutput.ScriptPubKey.GetDestinationAddress(network)}");
                Logger.LogInfo($"Change Output: {changeOutput.Amount.ToString(false, true)} {changeOutput.ScriptPubKey.GetDestinationAddress(network)}");
                Logger.LogInfo($"TxId: {res.Transaction.GetHash()}");

                foundReceive = false;
                Assert.InRange(res.Transaction.Transaction.Outputs.Count, 1, 2);
                foreach (var output in res.Transaction.Transaction.Outputs)
                {
                    if (output.ScriptPubKey == receive)
                    {
                        foundReceive = true;
                        Assert.Equal(amountToSend, output.Value);
                    }
                }
                Assert.True(foundReceive);

                #endregion LowFee

                #region MediumFee

                res = wallet.BuildTransaction(password, new PaymentIntent(receive, amountToSend, label: "foo"), FeeStrategy.OneDayConfirmationTargetStrategy, allowUnconfirmed: true);

                Assert.Equal(2, res.InnerWalletOutputs.Count());
                Assert.Empty(res.OuterWalletOutputs);
                activeOutput = res.InnerWalletOutputs.Single(x => x.ScriptPubKey == receive);
                changeOutput = res.InnerWalletOutputs.Single(x => x.ScriptPubKey != receive);

                Assert.Equal(receive, activeOutput.ScriptPubKey);
                Assert.Equal(amountToSend, activeOutput.Amount);
                Assert.Contains(res.Transaction.Transaction.Outputs, x => x.Value == changeOutput.Amount);
                Logger.LogInfo($"{nameof(res.Fee)}: {res.Fee}");
                Logger.LogInfo($"{nameof(res.FeePercentOfSent)}: {res.FeePercentOfSent} %");
                Logger.LogInfo($"{nameof(res.SpendsUnconfirmed)}: {res.SpendsUnconfirmed}");
                Logger.LogInfo($"Active Output: {activeOutput.Amount.ToString(false, true)} {activeOutput.ScriptPubKey.GetDestinationAddress(network)}");
                Logger.LogInfo($"Change Output: {changeOutput.Amount.ToString(false, true)} {changeOutput.ScriptPubKey.GetDestinationAddress(network)}");
                Logger.LogInfo($"TxId: {res.Transaction.GetHash()}");

                foundReceive = false;
                Assert.InRange(res.Transaction.Transaction.Outputs.Count, 1, 2);
                foreach (var output in res.Transaction.Transaction.Outputs)
                {
                    if (output.ScriptPubKey == receive)
                    {
                        foundReceive = true;
                        Assert.Equal(amountToSend, output.Value);
                    }
                }
                Assert.True(foundReceive);

                #endregion MediumFee

                #region HighFee

                res = wallet.BuildTransaction(password, new PaymentIntent(receive, amountToSend, label: "foo"), FeeStrategy.TwentyMinutesConfirmationTargetStrategy, allowUnconfirmed: true);

                Assert.Equal(2, res.InnerWalletOutputs.Count());
                Assert.Empty(res.OuterWalletOutputs);
                activeOutput = res.InnerWalletOutputs.Single(x => x.ScriptPubKey == receive);
                changeOutput = res.InnerWalletOutputs.Single(x => x.ScriptPubKey != receive);

                Assert.Equal(receive, activeOutput.ScriptPubKey);
                Assert.Equal(amountToSend, activeOutput.Amount);
                Assert.Contains(res.Transaction.Transaction.Outputs, x => x.Value == changeOutput.Amount);
                Logger.LogInfo($"{nameof(res.Fee)}: {res.Fee}");
                Logger.LogInfo($"{nameof(res.FeePercentOfSent)}: {res.FeePercentOfSent} %");
                Logger.LogInfo($"{nameof(res.SpendsUnconfirmed)}: {res.SpendsUnconfirmed}");
                Logger.LogInfo($"Active Output: {activeOutput.Amount.ToString(false, true)} {activeOutput.ScriptPubKey.GetDestinationAddress(network)}");
                Logger.LogInfo($"Change Output: {changeOutput.Amount.ToString(false, true)} {changeOutput.ScriptPubKey.GetDestinationAddress(network)}");
                Logger.LogInfo($"TxId: {res.Transaction.GetHash()}");

                foundReceive = false;
                Assert.InRange(res.Transaction.Transaction.Outputs.Count, 1, 2);
                foreach (var output in res.Transaction.Transaction.Outputs)
                {
                    if (output.ScriptPubKey == receive)
                    {
                        foundReceive = true;
                        Assert.Equal(amountToSend, output.Value);
                    }
                }
                Assert.True(foundReceive);

                Assert.InRange(res.Fee, Money.Zero, res.Fee);
                Assert.InRange(res.Fee, res.Fee, res.Fee);

                await broadcaster.SendTransactionAsync(res.Transaction);

                #endregion HighFee

                #region MaxAmount

                receive = keyManager.GetNextReceiveKey("MaxAmount", out _).P2wpkhScript;

                res = wallet.BuildTransaction(password, new PaymentIntent(receive, MoneyRequest.CreateAllRemaining(), "foo"), FeeStrategy.SevenDaysConfirmationTargetStrategy, allowUnconfirmed: true);

                Assert.Single(res.InnerWalletOutputs);
                Assert.Empty(res.OuterWalletOutputs);
                activeOutput = res.InnerWalletOutputs.Single();

                Assert.Equal(receive, activeOutput.ScriptPubKey);

                Assert.Single(res.Transaction.Transaction.Outputs);
                var maxBuiltTxOutput = res.Transaction.Transaction.Outputs.Single();
                Assert.Equal(receive, maxBuiltTxOutput.ScriptPubKey);
                Assert.Equal(wallet.Coins.Where(x => !x.Unavailable).Sum(x => x.Amount) - res.Fee, maxBuiltTxOutput.Value);

                await broadcaster.SendTransactionAsync(res.Transaction);

                #endregion MaxAmount

                #region InputSelection

                receive = keyManager.GetNextReceiveKey("InputSelection", out _).P2wpkhScript;

                var inputCountBefore = res.SpentCoins.Count();

                res = wallet.BuildTransaction(password, new PaymentIntent(receive, MoneyRequest.CreateAllRemaining(), "foo"), FeeStrategy.SevenDaysConfirmationTargetStrategy,
                                              allowUnconfirmed: true,
                                              allowedInputs: wallet.Coins.Where(x => !x.Unavailable).Select(x => x.OutPoint).Take(1));

                Assert.Single(res.InnerWalletOutputs);
                Assert.Empty(res.OuterWalletOutputs);
                activeOutput = res.InnerWalletOutputs.Single(x => x.ScriptPubKey == receive);

                Assert.True(inputCountBefore >= res.SpentCoins.Count());
                Assert.Equal(res.SpentCoins.Count(), res.Transaction.Transaction.Inputs.Count);

                Assert.Equal(receive, activeOutput.ScriptPubKey);
                Logger.LogInfo($"{nameof(res.Fee)}: {res.Fee}");
                Logger.LogInfo($"{nameof(res.FeePercentOfSent)}: {res.FeePercentOfSent} %");
                Logger.LogInfo($"{nameof(res.SpendsUnconfirmed)}: {res.SpendsUnconfirmed}");
                Logger.LogInfo($"Active Output: {activeOutput.Amount.ToString(false, true)} {activeOutput.ScriptPubKey.GetDestinationAddress(network)}");
                Logger.LogInfo($"TxId: {res.Transaction.GetHash()}");

                Assert.Single(res.Transaction.Transaction.Outputs);

                res = wallet.BuildTransaction(password, new PaymentIntent(receive, MoneyRequest.CreateAllRemaining(), "foo"), FeeStrategy.SevenDaysConfirmationTargetStrategy,
                                              allowUnconfirmed: true,
                                              allowedInputs: new[] { res.SpentCoins.Select(x => x.OutPoint).First() });

                Assert.Single(res.InnerWalletOutputs);
                Assert.Empty(res.OuterWalletOutputs);
                activeOutput = res.InnerWalletOutputs.Single(x => x.ScriptPubKey == receive);

                Assert.Single(res.Transaction.Transaction.Inputs);
                Assert.Single(res.Transaction.Transaction.Outputs);
                Assert.Single(res.SpentCoins);

                #endregion InputSelection

                #region Labeling

                Script receive2 = keyManager.GetNextReceiveKey("foo", out _).P2wpkhScript;
                res = wallet.BuildTransaction(password, new PaymentIntent(receive2, MoneyRequest.CreateAllRemaining(), "my label"), FeeStrategy.SevenDaysConfirmationTargetStrategy, allowUnconfirmed: true);

                Assert.Single(res.InnerWalletOutputs);
                Assert.Equal("foo, my label", res.InnerWalletOutputs.Single().Label);

                amountToSend = wallet.Coins.Where(x => !x.Unavailable).Sum(x => x.Amount) / 3;
                res          = wallet.BuildTransaction(
                    password,
                    new PaymentIntent(
                        new DestinationRequest(new Key(), amountToSend, label: "outgoing"),
                        new DestinationRequest(new Key(), amountToSend, label: "outgoing2")),
                    FeeStrategy.SevenDaysConfirmationTargetStrategy,
                    allowUnconfirmed: true);

                Assert.Single(res.InnerWalletOutputs);
                Assert.Equal(2, res.OuterWalletOutputs.Count());
                IEnumerable <string> change = res.InnerWalletOutputs.Single().Label.Labels;
                Assert.Contains("outgoing", change);
                Assert.Contains("outgoing2", change);

                await broadcaster.SendTransactionAsync(res.Transaction);

                IEnumerable <SmartCoin> unconfirmedCoins      = wallet.Coins.Where(x => x.Height == Height.Mempool).ToArray();
                IEnumerable <string>    unconfirmedCoinLabels = unconfirmedCoins.SelectMany(x => x.Label.Labels).ToArray();
                Assert.Contains("outgoing", unconfirmedCoinLabels);
                Assert.Contains("outgoing2", unconfirmedCoinLabels);
                IEnumerable <string> allKeyLabels = keyManager.GetKeys().SelectMany(x => x.Label.Labels);
                Assert.Contains("outgoing", allKeyLabels);
                Assert.Contains("outgoing2", allKeyLabels);

                Interlocked.Exchange(ref Common.FiltersProcessedByWalletCount, 0);
                await rpc.GenerateAsync(1);

                await Common.WaitForFiltersToBeProcessedAsync(TimeSpan.FromSeconds(120), 1);

                var bestHeight = new Height(bitcoinStore.SmartHeaderChain.TipHeight);
                IEnumerable <string> confirmedCoinLabels = wallet.Coins.Where(x => x.Height == bestHeight).SelectMany(x => x.Label.Labels);
                Assert.Contains("outgoing", confirmedCoinLabels);
                Assert.Contains("outgoing2", confirmedCoinLabels);
                allKeyLabels = keyManager.GetKeys().SelectMany(x => x.Label.Labels);
                Assert.Contains("outgoing", allKeyLabels);
                Assert.Contains("outgoing2", allKeyLabels);

                #endregion Labeling

                #region AllowedInputsDisallowUnconfirmed

                inputCountBefore = res.SpentCoins.Count();

                receive = keyManager.GetNextReceiveKey("AllowedInputsDisallowUnconfirmed", out _).P2wpkhScript;

                var allowedInputs = wallet.Coins.Where(x => !x.Unavailable).Select(x => x.OutPoint).Take(1);
                var toSend        = new PaymentIntent(receive, MoneyRequest.CreateAllRemaining(), "fizz");

                // covers:
                // disallow unconfirmed with allowed inputs
                res = wallet.BuildTransaction(password, toSend, FeeStrategy.TwentyMinutesConfirmationTargetStrategy, false, allowedInputs: allowedInputs);

                activeOutput = res.InnerWalletOutputs.Single(x => x.ScriptPubKey == receive);
                Assert.Single(res.InnerWalletOutputs);
                Assert.Empty(res.OuterWalletOutputs);

                Assert.Equal(receive, activeOutput.ScriptPubKey);
                Logger.LogDebug($"{nameof(res.Fee)}: {res.Fee}");
                Logger.LogDebug($"{nameof(res.FeePercentOfSent)}: {res.FeePercentOfSent} %");
                Logger.LogDebug($"{nameof(res.SpendsUnconfirmed)}: {res.SpendsUnconfirmed}");
                Logger.LogDebug($"Active Output: {activeOutput.Amount.ToString(false, true)} {activeOutput.ScriptPubKey.GetDestinationAddress(network)}");
                Logger.LogDebug($"TxId: {res.Transaction.GetHash()}");

                Assert.True(inputCountBefore >= res.SpentCoins.Count());
                Assert.False(res.SpendsUnconfirmed);

                Assert.Single(res.Transaction.Transaction.Inputs);
                Assert.Single(res.Transaction.Transaction.Outputs);
                Assert.Single(res.SpentCoins);

                Assert.True(inputCountBefore >= res.SpentCoins.Count());
                Assert.Equal(res.SpentCoins.Count(), res.Transaction.Transaction.Inputs.Count);

                #endregion AllowedInputsDisallowUnconfirmed

                #region CustomChange

                // covers:
                // customchange
                // feePc > 1
                var k1 = new Key();
                var k2 = new Key();
                res = wallet.BuildTransaction(
                    password,
                    new PaymentIntent(
                        new DestinationRequest(k1, MoneyRequest.CreateChange()),
                        new DestinationRequest(k2, Money.Coins(0.0003m), label: "outgoing")),
                    FeeStrategy.TwentyMinutesConfirmationTargetStrategy);

                Assert.Contains(k1.ScriptPubKey, res.OuterWalletOutputs.Select(x => x.ScriptPubKey));
                Assert.Contains(k2.ScriptPubKey, res.OuterWalletOutputs.Select(x => x.ScriptPubKey));

                #endregion CustomChange

                #region FeePcHigh

                res = wallet.BuildTransaction(
                    password,
                    new PaymentIntent(new Key(), Money.Coins(0.0003m), label: "outgoing"),
                    FeeStrategy.TwentyMinutesConfirmationTargetStrategy);

                Assert.True(res.FeePercentOfSent > 1);

                var newChangeK = keyManager.GenerateNewKey("foo", KeyState.Clean, isInternal: true);
                res = wallet.BuildTransaction(
                    password,
                    new PaymentIntent(
                        new DestinationRequest(newChangeK.P2wpkhScript, MoneyRequest.CreateChange(), "boo"),
                        new DestinationRequest(new Key(), Money.Coins(0.0003m), label: "outgoing")),
                    FeeStrategy.TwentyMinutesConfirmationTargetStrategy);

                Assert.True(res.FeePercentOfSent > 1);
                Assert.Single(res.OuterWalletOutputs);
                Assert.Single(res.InnerWalletOutputs);
                SmartCoin changeRes = res.InnerWalletOutputs.Single();
                Assert.Equal(newChangeK.P2wpkhScript, changeRes.ScriptPubKey);
                Assert.Equal(newChangeK.Label, changeRes.Label);
                Assert.Equal(KeyState.Clean, newChangeK.KeyState);                 // Still clean, because the tx wasn't yet propagated.

                #endregion FeePcHigh
            }
            finally
            {
                bitcoinStore.IndexStore.NewFilter -= Common.Wallet_NewFilterProcessed;
                await walletManager.RemoveAndStopAllAsync(CancellationToken.None);

                // Dispose wasabi synchronizer service.
                if (synchronizer is { })
 public async Task DequeueCoinsFromMixAsync(SmartCoin coin, string reason)
 {
     await DequeueCoinsFromMixAsync(new[] { coin }, reason);
 }
        private (HdPubKey change, IEnumerable <HdPubKey> active) GetOutputsToRegister(Money baseDenomination, int mixingLevelCount, IEnumerable <TxoRef> coinsToRegister)
        {
            // Figure out how many mixing level we need to register active outputs.
            Money inputSum = Money.Zero;

            foreach (TxoRef coinReference in coinsToRegister)
            {
                SmartCoin coin = State.GetSingleOrDefaultFromWaitingList(coinReference);
                inputSum += coin.Amount;
            }

            int maximumMixingLevelCount = 1;
            var denominations           = new List <Money>
            {
                baseDenomination
            };

            for (int i = 1; i < mixingLevelCount; i++)
            {
                Money denom = denominations.Last() * 2;
                denominations.Add(denom);
                if (inputSum > denom)
                {
                    maximumMixingLevelCount = i + 1;
                }
            }

            string changeLabel = "ZeroLink Change";
            string activeLabel = "ZeroLink Mixed Coin";

            var keysToSurelyRegister = ExposedLinks.Where(x => coinsToRegister.Contains(x.Key)).SelectMany(x => x.Value).Select(x => x.Key).ToArray();
            var keysTryNotToRegister = ExposedLinks.SelectMany(x => x.Value).Select(x => x.Key).Except(keysToSurelyRegister).ToArray();

            // Get all locked internal keys we have and assert we have enough.
            KeyManager.AssertLockedInternalKeysIndexed(howMany: maximumMixingLevelCount + 1);
            IEnumerable <HdPubKey> allLockedInternalKeys = KeyManager.GetKeys(x => x.IsInternal && x.KeyState == KeyState.Locked && !keysTryNotToRegister.Contains(x));

            // If any of our inputs have exposed address relationship then prefer that.
            allLockedInternalKeys = keysToSurelyRegister.Concat(allLockedInternalKeys).Distinct();

            // Prefer not to bloat the wallet:
            if (allLockedInternalKeys.Count() <= maximumMixingLevelCount)
            {
                allLockedInternalKeys = allLockedInternalKeys.Concat(keysTryNotToRegister).Distinct();
            }

            var newKeys = new List <HdPubKey>();

            for (int i = allLockedInternalKeys.Count(); i <= maximumMixingLevelCount + 1; i++)
            {
                HdPubKey k = KeyManager.GenerateNewKey("", KeyState.Locked, isInternal: true, toFile: false);
                newKeys.Add(k);
            }
            allLockedInternalKeys = allLockedInternalKeys.Concat(newKeys);

            // Select the change and active keys to register and label them accordingly.
            HdPubKey change = allLockedInternalKeys.First();

            change.SetLabel(changeLabel);

            var actives = new List <HdPubKey>();

            foreach (HdPubKey active in allLockedInternalKeys.Skip(1).Take(maximumMixingLevelCount))
            {
                actives.Add(active);
                active.SetLabel(activeLabel);
            }

            // Remember which links we are exposing.
            var outLinks = new List <HdPubKeyBlindedPair>
            {
                new HdPubKeyBlindedPair(change, isBlinded: false)
            };

            foreach (var active in actives)
            {
                outLinks.Add(new HdPubKeyBlindedPair(active, isBlinded: true));
            }
            foreach (TxoRef coin in coinsToRegister)
            {
                if (!ExposedLinks.TryAdd(coin, outLinks))
                {
                    var newOutLinks = new List <HdPubKeyBlindedPair>();
                    foreach (HdPubKeyBlindedPair link in ExposedLinks[coin])
                    {
                        newOutLinks.Add(link);
                    }
                    foreach (HdPubKeyBlindedPair link in outLinks)
                    {
                        HdPubKeyBlindedPair found = newOutLinks.FirstOrDefault(x => x == link);

                        if (found == default)
                        {
                            newOutLinks.Add(link);
                        }
                        else                         // If already in it then update the blinded value if it's getting exposed just now. (eg. the change)
                        {
                            if (found.IsBlinded)
                            {
                                found.IsBlinded = link.IsBlinded;
                            }
                        }
                    }

                    ExposedLinks[coin] = newOutLinks;
                }
            }

            // Save our modifications in the keymanager before we give back the selected keys.
            KeyManager.ToFile();
            return(change, actives);
        }