예제 #1
0
    public void CanGenerateKeys()
    {
        string password = "******";
        var    network  = Network.Main;
        var    manager  = KeyManager.CreateNew(out _, password, network);

        var k1 = manager.GenerateNewKey(SmartLabel.Empty, KeyState.Clean, true);
        var k2 = manager.GenerateNewKey(label: null !, KeyState.Clean, true);

        Assert.Equal(SmartLabel.Empty, k1.Label);
        Assert.Equal(SmartLabel.Empty, k2.Label);

        for (int i = 0; i < 1000; i++)
        {
            var isInternal   = Random.Shared.Next(2) == 0;
            var label        = RandomString.AlphaNumeric(21);
            var keyState     = (KeyState)Random.Shared.Next(3);
            var generatedKey = manager.GenerateNewKey(label, keyState, isInternal);

            Assert.Equal(isInternal, generatedKey.IsInternal);
            Assert.Equal(label, generatedKey.Label);
            Assert.Equal(keyState, generatedKey.KeyState);
            Assert.StartsWith(KeyManager.GetAccountKeyPath(network).ToString(), generatedKey.FullKeyPath.ToString());
        }
    }
예제 #2
0
    public void GapCountingTests()
    {
        var km = KeyManager.CreateNew(out _, "", Network.Main);

        Assert.Equal(0, km.CountConsecutiveUnusedKeys(true));
        Assert.Equal(0, km.CountConsecutiveUnusedKeys(false));

        var k = km.GenerateNewKey("", KeyState.Clean, true);

        Assert.Equal(1, km.CountConsecutiveUnusedKeys(true));

        km.GenerateNewKey("", KeyState.Locked, true);
        Assert.Equal(2, km.CountConsecutiveUnusedKeys(true));

        k.SetKeyState(KeyState.Used);
        Assert.Equal(1, km.CountConsecutiveUnusedKeys(true));

        for (int i = 0; i < 100; i++)
        {
            var k2 = km.GenerateNewKey("", KeyState.Clean, true);
            if (i == 50)
            {
                k = k2;
            }
        }
        Assert.Equal(101, km.CountConsecutiveUnusedKeys(true));
        k.SetKeyState(KeyState.Locked);
        Assert.Equal(101, km.CountConsecutiveUnusedKeys(true));
        k.SetKeyState(KeyState.Used);
        Assert.Equal(51, km.CountConsecutiveUnusedKeys(true));
    }
예제 #3
0
        public void CompatibilityTest()
        {
            string buggy    = "    w¾3AÍ-dCdï×¾M\\Øò¹ãÔÕýÈÝÁÐ9oEp¨}r:SR¦·ßNó±¥*W!¢ê#ikÇå<ðtÇf·a\\]§,à±H7«®È4nèNmæo4.qØ-¾ûda¯";
            string original = "    w¾3AÍ-dCdï×¾M\\Øò¹ãÔÕýÈÝÁÐ9oEp¨}r:SR¦·ßNó±¥*W!¢ê#ikÇå<ðtÇf·a\\]§,à±H7«®È4nèNmæo4.qØ-¾ûda¯ºíö¾,¥¢½\\¹õèKeÁìÍSÈ@r±ØÙ2[r©UQÞ¶xN\"?:Ö@°&\n";

            Assert.Throws <FormatException>(() => PasswordHelper.Guard(buggy));

            Assert.True(PasswordHelper.IsTrimable(buggy, out buggy));

            // Creating a wallet with buggy password.
            var keyManager = KeyManager.CreateNew(out _, buggy);

            Assert.True(PasswordHelper.IsTrimable(original, out original));

            Logger.TurnOff();
            Assert.False(PasswordHelper.TryPassword(keyManager, "falsepassword", out _));

            // This should pass
            Assert.NotNull(PasswordHelper.GetMasterExtKey(keyManager, original, out _));

            Assert.True(PasswordHelper.TryPassword(keyManager, buggy, out string compatiblePasswordNotUsed));
            Assert.Null(compatiblePasswordNotUsed);

            Assert.True(PasswordHelper.TryPassword(keyManager, original, out string compatiblePassword));
            Assert.Equal(buggy, compatiblePassword);
            Logger.TurnOn();
        }
        private async Task <TransactionProcessor> CreateTransactionProcessorAsync([CallerMemberName] string callerName = "")
        {
            var datadir = EnvironmentHelpers.GetDataDir(Path.Combine("WalletWasabi", "Bench"));
            var dir     = Path.Combine(datadir, callerName, "TransactionStore");

            Console.WriteLine(dir);
            await IoHelpers.DeleteRecursivelyWithMagicDustAsync(dir);

            // Create the services.
            // 1. Create connection service.
            var nodes                = new NodesGroup(Network.Main);
            var bitcoinStore         = new BitcoinStore();
            var serviceConfiguration = new ServiceConfiguration(2, 2, 21, 50, new IPEndPoint(IPAddress.Parse("127.0.0.1"), 45678), Money.Coins(0.0001m));

            // 2. Create wasabi synchronizer service.
            var synchronizer = new WasabiSynchronizer(Network.Main, bitcoinStore, () => new Uri("http://localhost:35474"), null);

            synchronizer.Start(requestInterval: TimeSpan.FromDays(1), TimeSpan.FromDays(1), 1000);

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

            // 4. Create chaumian coinjoin client.
            var chaumianClient = new CoinJoinClient(synchronizer, Network.Main, keyManager);

            // 5. Create wallet service.
            await bitcoinStore.InitializeAsync(dir, Network.Main);

            var workDir      = Path.Combine(datadir, EnvironmentHelpers.GetMethodName());
            var feeProviders = new FeeProviders(new[] { synchronizer });

            var wallet = new WalletService(bitcoinStore, keyManager, synchronizer, nodes, workDir, serviceConfiguration, feeProviders);

            return(wallet.TransactionProcessor);
        }
예제 #5
0
    public void SmartCoinEquality()
    {
        var tx    = Transaction.Parse("0100000000010176f521a178a4b394b647169a89b29bb0d95b6bce192fd686d533eb4ea98464a20000000000ffffffff02ed212d020000000016001442da650f25abde0fef57badd745df7346e3e7d46fbda6400000000001976a914bd2e4029ce7d6eca7d2c3779e8eac36c952afee488ac02483045022100ea43ccf95e1ac4e8b305c53761da7139dbf6ff164e137a6ce9c09e15f316c22902203957818bc505bbafc181052943d7ab1f3ae82c094bf749813e8f59108c6c268a012102e59e61f20c7789aa73faf5a92dc8c0424e538c635c55d64326d95059f0f8284200000000", Network.TestNet);
        var index = 0U;
        var km    = KeyManager.CreateNew(out _, "", Network.Main);
        var hdpk1 = km.GenerateNewKey(SmartLabel.Empty, KeyState.Clean, false);
        var hdpk2 = km.GenerateNewKey(SmartLabel.Empty, KeyState.Clean, false);

        tx.Outputs[0].ScriptPubKey = hdpk1.P2wpkhScript;
        tx.Outputs[1].ScriptPubKey = hdpk2.P2wpkhScript;

        var height = Height.Mempool;
        var stx    = new SmartTransaction(tx, height);

        var coin = new SmartCoin(stx, index, hdpk1);

        // If the txId or the index differ, equality should think it's a different coin.
        var differentCoin = new SmartCoin(stx, index + 1, hdpk2);

        // If the txId and the index are the same, equality should think it's the same coin.
        var sameCoin = new SmartCoin(stx, index, hdpk1);

        Assert.Equal(coin, sameCoin);
        Assert.NotEqual(coin, differentCoin);
    }
예제 #6
0
        public void CanGenerateKeys()
        {
            string password = "******";
            var    manager  = KeyManager.CreateNew(out _, password);

            var random = new Random();

            var k1 = manager.GenerateNewKey("", KeyState.Clean, true);
            var k2 = manager.GenerateNewKey(null, KeyState.Clean, true);

            Assert.Equal("", k1.Label);
            Assert.Equal("", k2.Label);

            for (int i = 0; i < 1000; i++)
            {
                var isInternal   = random.Next(2) == 0;
                var label        = RandomString.Generate(21);
                var keyState     = (KeyState)random.Next(3);
                var generatedKey = manager.GenerateNewKey(label, keyState, isInternal);

                Assert.Equal(isInternal, generatedKey.IsInternal);
                Assert.Equal(label, generatedKey.Label);
                Assert.Equal(keyState, generatedKey.KeyState);
                Assert.StartsWith("84'/0'/0'", generatedKey.FullKeyPath.ToString());
            }
        }
예제 #7
0
        public void FormattingTest()
        {
            string buggy    = "    w¾3AÍ-dCdï×¾M\\Øò¹ãÔÕýÈÝÁÐ9oEp¨}r:SR¦·ßNó±¥*W!¢ê#ikÇå<ðtÇf·a\\]§,à±H7«®È4nèNmæo4.qØ-¾ûda¯";
            string original = "    w¾3AÍ-dCdï×¾M\\Øò¹ãÔÕýÈÝÁÐ9oEp¨}r:SR¦·ßNó±¥*W!¢ê#ikÇå<ðtÇf·a\\]§,à±H7«®È4nèNmæo4.qØ-¾ûda¯ºíö¾,¥¢½\\¹õèKeÁìÍSÈ@r±ØÙ2[r©UQÞ¶xN\"?:Ö@°&\n";

            // Creating a wallet with buggy password.
            var keyManager = KeyManager.CreateNew(out _, buggy);

            // Password should be formatted, before entering here.
            Assert.Throws <FormatException>(() => PasswordHelper.GetMasterExtKey(keyManager, original, out _));

            // This should not throw format exception but pw is not correct.
            Assert.Throws <SecurityException>(() => PasswordHelper.GetMasterExtKey(keyManager, RandomString.Generate(PasswordHelper.MaxPasswordLength), out _));

            // Password should be formatted, before entering here.
            Assert.Throws <FormatException>(() => PasswordHelper.GetMasterExtKey(keyManager, RandomString.Generate(PasswordHelper.MaxPasswordLength + 1), out _));

            // Too long password with extra spaces.
            var badPassword = $"   {RandomString.Generate(PasswordHelper.MaxPasswordLength + 1)}   ";

            // Password should be formatted, before entering here.
            Assert.Throws <FormatException>(() => PasswordHelper.GetMasterExtKey(keyManager, badPassword, out _));

            Assert.True(PasswordHelper.IsTrimable(badPassword, out badPassword));

            // Still too long.
            Assert.Throws <FormatException>(() => PasswordHelper.GetMasterExtKey(keyManager, badPassword, out _));

            Assert.True(PasswordHelper.IsTooLong(badPassword, out badPassword));

            // This should not throw format exception but pw is not correct.
            Assert.Throws <SecurityException>(() => PasswordHelper.GetMasterExtKey(keyManager, badPassword, out _));
        }
예제 #8
0
        private static TransactionProcessor CreateTransactionProcessor()
        {
            var keyManager = KeyManager.CreateNew(out _, "password");

            keyManager.AssertCleanKeysIndexed();
            return(new TransactionProcessor(
                       keyManager,
                       new ObservableConcurrentHashSet <SmartCoin>(),
                       Money.Coins(0.0001m),
                       new ConcurrentHashSet <SmartTransaction>()));
        }
예제 #9
0
    public void CanHandleGap()
    {
        string password = "******";
        var    manager  = KeyManager.CreateNew(out _, password, Network.Main);

        manager.AssertCleanKeysIndexed();
        var lastKey = manager.GetKeys(KeyState.Clean, isInternal: false).Last();

        lastKey.SetKeyState(KeyState.Used);
        var newKeys = manager.AssertCleanKeysIndexed();

        Assert.Equal(manager.MinGapLimit, newKeys.Count());
    }
예제 #10
0
        public void CanCreateNew()
        {
            string password = "******";
            var    manager  = KeyManager.CreateNew(out Mnemonic mnemonic, password);
            var    manager2 = KeyManager.CreateNew(out Mnemonic mnemonic2, "");
            var    manager3 = KeyManager.CreateNew(out _, "P@ssw0rdé");

            Assert.Equal(12, mnemonic.ToString().Split(' ').Length);
            Assert.Equal(12, mnemonic2.ToString().Split(' ').Length);
            Assert.Equal(12, mnemonic2.ToString().Split(' ').Length);

            Assert.NotNull(manager.ChainCode);
            Assert.NotNull(manager.EncryptedSecret);
            Assert.NotNull(manager.ExtPubKey);

            Assert.NotNull(manager2.ChainCode);
            Assert.NotNull(manager2.EncryptedSecret);
            Assert.NotNull(manager2.ExtPubKey);

            Assert.NotNull(manager3.ChainCode);
            Assert.NotNull(manager3.EncryptedSecret);
            Assert.NotNull(manager3.ExtPubKey);

            var sameManager  = new KeyManager(manager.EncryptedSecret, manager.ChainCode, manager.ExtPubKey);
            var sameManager2 = new KeyManager(manager.EncryptedSecret, manager.ChainCode, password);

            Logger.TurnOff();
            Assert.Throws <SecurityException>(() => new KeyManager(manager.EncryptedSecret, manager.ChainCode, "differentPassword"));
            Logger.TurnOn();

            Assert.Equal(manager.ChainCode, sameManager.ChainCode);
            Assert.Equal(manager.EncryptedSecret, sameManager.EncryptedSecret);
            Assert.Equal(manager.ExtPubKey, sameManager.ExtPubKey);

            Assert.Equal(manager.ChainCode, sameManager2.ChainCode);
            Assert.Equal(manager.EncryptedSecret, sameManager2.EncryptedSecret);
            Assert.Equal(manager.ExtPubKey, sameManager2.ExtPubKey);

            var differentManager = KeyManager.CreateNew(out Mnemonic mnemonic4, password);

            Assert.NotEqual(mnemonic, mnemonic4);
            Assert.NotEqual(manager.ChainCode, differentManager.ChainCode);
            Assert.NotEqual(manager.EncryptedSecret, differentManager.EncryptedSecret);
            Assert.NotEqual(manager.ExtPubKey, differentManager.ExtPubKey);

            var manager5 = new KeyManager(manager2.EncryptedSecret, manager2.ChainCode, password: null);

            Assert.Equal(manager2.ChainCode, manager5.ChainCode);
            Assert.Equal(manager2.EncryptedSecret, manager5.EncryptedSecret);
            Assert.Equal(manager2.ExtPubKey, manager5.ExtPubKey);
        }
        private async Task <TransactionProcessor> CreateTransactionProcessorAsync([CallerMemberName] string callerName = "")
        {
            var keyManager = KeyManager.CreateNew(out _, "password");

            keyManager.AssertCleanKeysIndexed();

            var txStore = new AllTransactionStore();
            var dir     = Path.Combine(Global.Instance.DataDir, callerName, "TransactionStore");
            await IoHelpers.DeleteRecursivelyWithMagicDustAsync(dir);

            await txStore.InitializeAsync(dir, Network.RegTest);

            return(new TransactionProcessor(
                       txStore,
                       keyManager,
                       Money.Coins(0.0001m)));
        }
    public void SelectNonPrivateCoinFromOneCoinSetOfCoins()
    {
        // This test is to make sure that we select the only non-private coin when it is the only coin in the wallet.
        const int AnonymitySet      = 10;
        var       km                = KeyManager.CreateNew(out _, "", Network.Main);
        var       coinsToSelectFrom = Enumerable
                                      .Empty <SmartCoin>()
                                      .Prepend(BitcoinFactory.CreateSmartCoin(BitcoinFactory.CreateHdPubKey(km), Money.Coins(1m), 0, anonymitySet: AnonymitySet - 1))
                                      .ToList();

        var coins = CoinJoinClient.SelectCoinsForRound(
            coins: coinsToSelectFrom,
            CreateMultipartyTransactionParameters(),
            consolidationMode: false,
            anonScoreTarget: AnonymitySet,
            ConfigureRng(1));

        Assert.Single(coins);
    }
예제 #13
0
    public void SignTransactionTest()
    {
        var keyManager          = KeyManager.CreateNew(out _, "", Network.Main);
        var destinationProvider = new InternalDestinationProvider(keyManager);
        var keyChain            = new KeyChain(keyManager, new Kitchen(""));

        var coinDestination = destinationProvider.GetNextDestinations(1).First();
        var coin            = new Coin(BitcoinFactory.CreateOutPoint(), new TxOut(Money.Coins(1.0m), coinDestination));
        var ownershipProof  = keyChain.GetOwnershipProof(coinDestination, new CoinJoinInputCommitmentData("test", uint256.One));

        var transaction = Transaction.Create(Network.Main);         // the transaction doesn't contain the input that we request to be signed.

        Assert.Throws <ArgumentException>(() => keyChain.Sign(transaction, coin, ownershipProof));

        transaction.Inputs.Add(coin.Outpoint);
        var signedTx = keyChain.Sign(transaction, coin, ownershipProof);

        Assert.True(signedTx.HasWitness);
    }
    public void TwoNonPrivateCoinInSetOfCoins()
    {
        const int MinAnonimitySet = 10;
        var       km = KeyManager.CreateNew(out _, "", Network.Main);
        var       coinsToSelectFrom = Enumerable
                                      .Empty <SmartCoin>()
                                      .Prepend(BitcoinFactory.CreateSmartCoin(BitcoinFactory.CreateHdPubKey(km), Money.Coins(1m), 0, anonymitySet: MinAnonimitySet - 1))
                                      .Prepend(BitcoinFactory.CreateSmartCoin(BitcoinFactory.CreateHdPubKey(km), Money.Coins(1m), 0, anonymitySet: MinAnonimitySet - 1))
                                      .ToList();

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

        Assert.Single(coins);
    }
    public void SelectNothingFromFullyPrivateSetOfCoins()
    {
        // This test is to make sure no coins are selected when all coins are private.
        const int AnonymitySet      = 10;
        var       km                = KeyManager.CreateNew(out _, "", Network.Main);
        var       coinsToSelectFrom = Enumerable
                                      .Range(0, 10)
                                      .Select(i => BitcoinFactory.CreateSmartCoin(BitcoinFactory.CreateHdPubKey(km), Money.Coins(1m), 0, anonymitySet: AnonymitySet + 1))
                                      .ToList();

        var coins = CoinJoinClient.SelectCoinsForRound(
            coins: coinsToSelectFrom,
            CreateMultipartyTransactionParameters(),
            consolidationMode: false,
            anonScoreTarget: AnonymitySet,
            ConfigureRng(5));

        Assert.Empty(coins);
    }
예제 #16
0
        public async Task SetPasswordAsync(string password)
        {
            Mnemonic seedWords = null;
            await Task.Run(() =>
            {
                // Here we are not letting anything that will be autocorrected later.
                // Generate wallet with password exactly as entered for compatibility.
                PasswordHelper.Guard(password);

                string walletFilePath = Path.Combine(_walletManager.WalletDirectories.WalletsDir, $"{_config.Network}.json");
                KeyManager.CreateNew(out seedWords, password, walletFilePath);
            });

            await _storage.SetSeedWords(password, seedWords.ToString());

            // this should not be a config
            _uiConfig.HasSeed = true;
            _uiConfig.ToFile();
        }
    public void SelectTwoNonPrivateCoinsFromTwoCoinsSetOfCoinsConsolidationMode()
    {
        // This test is to make sure that we select more than one non-private coin.
        const int AnonymitySet      = 10;
        var       km                = KeyManager.CreateNew(out _, "", Network.Main);
        var       coinsToSelectFrom = Enumerable
                                      .Empty <SmartCoin>()
                                      .Prepend(BitcoinFactory.CreateSmartCoin(BitcoinFactory.CreateHdPubKey(km), Money.Coins(1m), 0, anonymitySet: AnonymitySet - 1))
                                      .Prepend(BitcoinFactory.CreateSmartCoin(BitcoinFactory.CreateHdPubKey(km), Money.Coins(1m), 0, anonymitySet: AnonymitySet - 1))
                                      .ToList();

        var coins = CoinJoinClient.SelectCoinsForRound(
            coins: coinsToSelectFrom,
            CreateMultipartyTransactionParameters(),
            consolidationMode: true,
            anonScoreTarget: AnonymitySet,
            ConfigureRng(1));

        Assert.Equal(2, coins.Count);
    }
예제 #18
0
        public void CanSerialize()
        {
            string password = "******";

            var filePath = Path.Combine(SharedFixture.DataDir, nameof(CanSerialize), "Wallet.json");

            DeleteFileAndDirectoryIfExists(filePath);

            Logger.TurnOff();
            Assert.Throws <FileNotFoundException>(() => KeyManager.FromFile(filePath));
            Logger.TurnOn();

            var manager = KeyManager.CreateNew(out _, password, filePath);

            KeyManager.FromFile(filePath);

            manager.ToFile();

            manager.ToFile();             // assert it doesn't throw

            var random = new Random();

            for (int i = 0; i < 1000; i++)
            {
                var isInternal = random.Next(2) == 0;
                var label      = RandomString.Generate(21);
                var keyState   = (KeyState)random.Next(3);
                manager.GenerateNewKey(label, keyState, isInternal, toFile: false);
            }
            manager.ToFile();

            Assert.True(File.Exists(filePath));

            var sameManager = KeyManager.FromFile(filePath);

            Assert.Equal(manager.ChainCode, sameManager.ChainCode);
            Assert.Equal(manager.EncryptedSecret, sameManager.EncryptedSecret);
            Assert.Equal(manager.ExtPubKey, sameManager.ExtPubKey);

            DeleteFileAndDirectoryIfExists(filePath);
        }
    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);
    }
예제 #20
0
    public void CanSerialize()
    {
        string password = "******";

        var filePath = Path.Combine(Common.GetWorkDir(), "Wallet.json");

        DeleteFileAndDirectoryIfExists(filePath);

        Logger.TurnOff();
        Assert.Throws <FileNotFoundException>(() => KeyManager.FromFile(filePath));
        Logger.TurnOn();

        var manager = KeyManager.CreateNew(out _, password, Network.Main, filePath);

        KeyManager.FromFile(filePath);

        manager.ToFile();

        manager.ToFile();         // assert it does not throw

        for (int i = 0; i < 1000; i++)
        {
            var isInternal = Random.Shared.Next(2) == 0;
            var label      = RandomString.AlphaNumeric(21);
            var keyState   = (KeyState)Random.Shared.Next(3);
            manager.GenerateNewKey(label, keyState, isInternal, toFile: false);
        }
        manager.ToFile();

        Assert.True(File.Exists(filePath));

        var sameManager = KeyManager.FromFile(filePath);

        Assert.Equal(manager.ChainCode, sameManager.ChainCode);
        Assert.Equal(manager.EncryptedSecret, sameManager.EncryptedSecret);
        Assert.Equal(manager.ExtPubKey, sameManager.ExtPubKey);

        DeleteFileAndDirectoryIfExists(filePath);
    }
예제 #21
0
        public async Task ReorgTestAsync()
        {
            (string password, IRPCClient rpc, Network network, _, _, BitcoinStore bitcoinStore, Backend.Global global) = await Common.InitializeTestEnvironmentAsync(RegTestFixture, 1);

            var keyManager = KeyManager.CreateNew(out _, password, network);

            // Mine some coins, make a few bech32 transactions then make it confirm.
            await rpc.GenerateAsync(1);

            var key = keyManager.GenerateNewKey(SmartLabel.Empty, KeyState.Clean, isInternal: false);
            var tx2 = await rpc.SendToAddressAsync(key.GetP2wpkhAddress(network), Money.Coins(0.1m));

            key = keyManager.GenerateNewKey(SmartLabel.Empty, KeyState.Clean, isInternal: false);
            var tx3 = await rpc.SendToAddressAsync(key.GetP2wpkhAddress(network), Money.Coins(0.1m));

            var tx4 = await rpc.SendToAddressAsync(key.GetP2pkhAddress(network), Money.Coins(0.1m));

            var tx5 = await rpc.SendToAddressAsync(key.GetP2shOverP2wpkhAddress(network), Money.Coins(0.1m));

            var tx1 = await rpc.SendToAddressAsync(key.GetP2wpkhAddress(network), Money.Coins(0.1m), replaceable : true);

            await rpc.GenerateAsync(2);             // Generate two, so we can test for two reorg

            var node = RegTestFixture.BackendRegTestNode;

            using HttpClientFactory httpClientFactory = new(torEndPoint : null, backendUriGetter : () => new Uri(RegTestFixture.BackendEndPoint));
            WasabiSynchronizer synchronizer = new(bitcoinStore, httpClientFactory);

            try
            {
                synchronizer.Start(requestInterval: TimeSpan.FromSeconds(3), 1000);

                var reorgAwaiter = new EventsAwaiter <FilterModel>(
                    h => bitcoinStore.IndexStore.Reorged += h,
                    h => bitcoinStore.IndexStore.Reorged -= h,
                    2);

                // Test initial synchronization.
                await WaitForIndexesToSyncAsync(global, TimeSpan.FromSeconds(90), bitcoinStore);

                var tip = await rpc.GetBestBlockHashAsync();

                Assert.Equal(tip, bitcoinStore.SmartHeaderChain.TipHash);
                var tipBlock = await rpc.GetBlockHeaderAsync(tip);

                Assert.Equal(tipBlock.HashPrevBlock, bitcoinStore.SmartHeaderChain.GetChain().Select(x => x.header.BlockHash).ToArray()[bitcoinStore.SmartHeaderChain.HashCount - 2]);

                // Test synchronization after fork.
                await rpc.InvalidateBlockAsync(tip);                 // Reorg 1

                tip = await rpc.GetBestBlockHashAsync();

                await rpc.InvalidateBlockAsync(tip);                 // Reorg 2

                var tx1bumpRes = await rpc.BumpFeeAsync(tx1);        // RBF it

                await rpc.GenerateAsync(5);
                await WaitForIndexesToSyncAsync(global, TimeSpan.FromSeconds(90), bitcoinStore);

                var hashes = bitcoinStore.SmartHeaderChain.GetChain().Select(x => x.header.BlockHash).ToArray();
                Assert.DoesNotContain(tip, hashes);
                Assert.DoesNotContain(tipBlock.HashPrevBlock, hashes);

                tip = await rpc.GetBestBlockHashAsync();

                Assert.Equal(tip, bitcoinStore.SmartHeaderChain.TipHash);

                var filterList = new List <FilterModel>();
                await bitcoinStore.IndexStore.ForeachFiltersAsync(async x =>
                {
                    filterList.Add(x);
                    await Task.CompletedTask;
                },
                                                                  new Height(0));

                var filterTip = filterList.Last();
                Assert.Equal(tip, filterTip.Header.BlockHash);

                // Test filter block hashes are correct after fork.
                var blockCountIncludingGenesis = await rpc.GetBlockCountAsync() + 1;

                filterList.Clear();
                await bitcoinStore.IndexStore.ForeachFiltersAsync(async x =>
                {
                    filterList.Add(x);
                    await Task.CompletedTask;
                },
                                                                  new Height(0));

                FilterModel[] filters = filterList.ToArray();
                for (int i = 0; i < blockCountIncludingGenesis; i++)
                {
                    var expectedHash = await rpc.GetBlockHashAsync(i);

                    var filter = filters[i];
                    Assert.Equal(i, (int)filter.Header.Height);
                    Assert.Equal(expectedHash, filter.Header.BlockHash);
                    if (i < 101)                     // Later other tests may fill the filter.
                    {
                        Assert.Equal(IndexBuilderService.CreateDummyEmptyFilter(expectedHash).ToString(), filter.Filter.ToString());
                    }
                }

                // Test the serialization, too.
                tip = await rpc.GetBestBlockHashAsync();

                var blockHash = tip;
                for (var i = 0; i < hashes.Length; i++)
                {
                    var block = await rpc.GetBlockHeaderAsync(blockHash);

                    Assert.Equal(blockHash, hashes[hashes.Length - i - 1]);
                    blockHash = block.HashPrevBlock;
                }

                // Assert reorg happened exactly as many times as we reorged.
                await reorgAwaiter.WaitAsync(TimeSpan.FromSeconds(10));
            }
            finally
            {
                await synchronizer.StopAsync();
            }
        }
예제 #22
0
        public async Task WalletTestsAsync()
        {
            (string password, IRPCClient rpc, Network network, _, ServiceConfiguration serviceConfiguration, BitcoinStore bitcoinStore, Backend.Global global) = await Common.InitializeTestEnvironmentAsync(RegTestFixture, 1);

            // Create the services.
            // 1. Create connection service.
            NodesGroup nodes = new(global.Config.Network, requirements : Constants.NodeRequirements);

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

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

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

            // 2. Create wasabi synchronizer service.
            using HttpClientFactory httpClientFactory = new(torEndPoint : null, backendUriGetter : () => new Uri(RegTestFixture.BackendEndPoint));
            WasabiSynchronizer synchronizer = new(bitcoinStore, httpClientFactory);
            HybridFeeProvider  feeProvider  = new(synchronizer, null);

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

            // 4. Create wallet service.
            var workDir = Helpers.Common.GetWorkDir();

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

            using var wallet           = Wallet.CreateAndRegisterServices(network, bitcoinStore, keyManager, synchronizer, workDir, serviceConfiguration, feeProvider, blockProvider);
            wallet.NewFilterProcessed += Common.Wallet_NewFilterProcessed;

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

            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), 1000); // Start wasabi synchronizer service.
                await feeProvider.StartAsync(CancellationToken.None);

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

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

                using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30)))
                {
                    await wallet.StartAsync(cts.Token);                     // Initialize wallet service.
                }
                Assert.Equal(1, await blockProvider.BlockRepository.CountAsync(CancellationToken.None));

                Assert.Single(wallet.Coins);
                var firstCoin = wallet.Coins.Single();
                Assert.Equal(Money.Coins(0.1m), firstCoin.Amount);
                Assert.Equal(new Height((int)bitcoinStore.SmartHeaderChain.TipHeight), firstCoin.Height);
                Assert.InRange(firstCoin.Index, 0U, 1U);
                Assert.True(firstCoin.IsAvailable());
                Assert.Equal("foo label", firstCoin.HdPubKey.Label);
                Assert.Equal(key.P2wpkhScript, firstCoin.ScriptPubKey);
                Assert.Null(firstCoin.SpenderTransaction);
                Assert.Equal(txId, firstCoin.TransactionId);
                Assert.Single(keyManager.GetKeys(KeyState.Used, false));
                Assert.Equal("foo label", keyManager.GetKeys(KeyState.Used, false).Single().Label);

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

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

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

                await rpc.GenerateAsync(1);

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

                Assert.Equal(3, await blockProvider.BlockRepository.CountAsync(CancellationToken.None));

                Assert.Equal(3, wallet.Coins.Count());
                firstCoin = wallet.Coins.OrderBy(x => x.Height).First();
                var secondCoin = wallet.Coins.OrderBy(x => x.Height).Take(2).Last();
                var thirdCoin  = wallet.Coins.OrderBy(x => x.Height).Last();
                Assert.Equal(Money.Coins(0.01m), secondCoin.Amount);
                Assert.Equal(Money.Coins(0.02m), thirdCoin.Amount);
                Assert.Equal(new Height(bitcoinStore.SmartHeaderChain.TipHeight).Value - 2, firstCoin.Height.Value);
                Assert.Equal(new Height(bitcoinStore.SmartHeaderChain.TipHeight).Value - 1, secondCoin.Height.Value);
                Assert.Equal(new Height(bitcoinStore.SmartHeaderChain.TipHeight), thirdCoin.Height);
                Assert.True(thirdCoin.IsAvailable());
                Assert.Equal("foo label", firstCoin.HdPubKey.Label);
                Assert.Equal("bar label", secondCoin.HdPubKey.Label);
                Assert.Equal("bar label", thirdCoin.HdPubKey.Label);
                Assert.Equal(key.P2wpkhScript, firstCoin.ScriptPubKey);
                Assert.Equal(key2.P2wpkhScript, secondCoin.ScriptPubKey);
                Assert.Equal(key2.P2wpkhScript, thirdCoin.ScriptPubKey);
                Assert.Null(thirdCoin.SpenderTransaction);
                Assert.Equal(txId, firstCoin.TransactionId);
                Assert.Equal(txId2, secondCoin.TransactionId);
                Assert.Equal(txId3, thirdCoin.TransactionId);

                Assert.Equal(2, keyManager.GetKeys(KeyState.Used, false).Count());
                Assert.Empty(keyManager.GetKeys(KeyState.Used, true));
                Assert.Equal(2, keyManager.GetKeys(KeyState.Used).Count());
                Assert.Empty(keyManager.GetKeys(KeyState.Locked, false));
                Assert.Equal(14, keyManager.GetKeys(KeyState.Locked, true).Count());
                Assert.Equal(14, keyManager.GetKeys(KeyState.Locked).Count());
                Assert.Equal(21, keyManager.GetKeys(KeyState.Clean, true).Count());
                Assert.Equal(21, keyManager.GetKeys(KeyState.Clean, false).Count());
                Assert.Equal(42, keyManager.GetKeys(KeyState.Clean).Count());
                Assert.Equal(58, keyManager.GetKeys().Count());

                Assert.Single(keyManager.GetKeys(x => x.Label == "foo label" && x.KeyState == KeyState.Used && !x.IsInternal));
                Assert.Single(keyManager.GetKeys(x => x.Label == "bar label" && x.KeyState == KeyState.Used && !x.IsInternal));

                // REORG TESTS
                var txId4 = await rpc.SendToAddressAsync(key2.GetP2wpkhAddress(network), Money.Coins(0.03m), replaceable : true);

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

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

                Assert.NotEmpty(wallet.Coins.Where(x => x.TransactionId == txId4));
                var tip = await rpc.GetBestBlockHashAsync();

                await rpc.InvalidateBlockAsync(tip);                 // Reorg 1

                tip = await rpc.GetBestBlockHashAsync();

                await rpc.InvalidateBlockAsync(tip);                 // Reorg 2

                var tx4bumpRes = await rpc.BumpFeeAsync(txId4);      // RBF it

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

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

                Assert.Equal(4, await blockProvider.BlockRepository.CountAsync(CancellationToken.None));

                Assert.Equal(4, wallet.Coins.Count());
                Assert.Empty(wallet.Coins.Where(x => x.TransactionId == txId4));
                Assert.NotEmpty(wallet.Coins.Where(x => x.TransactionId == tx4bumpRes.TransactionId));
                var rbfCoin = wallet.Coins.Single(x => x.TransactionId == tx4bumpRes.TransactionId);

                Assert.Equal(Money.Coins(0.03m), rbfCoin.Amount);
                Assert.Equal(new Height(bitcoinStore.SmartHeaderChain.TipHeight).Value - 2, rbfCoin.Height.Value);
                Assert.True(rbfCoin.IsAvailable());
                Assert.Equal("bar label", rbfCoin.HdPubKey.Label);
                Assert.Equal(key2.P2wpkhScript, rbfCoin.ScriptPubKey);
                Assert.Null(rbfCoin.SpenderTransaction);
                Assert.Equal(tx4bumpRes.TransactionId, rbfCoin.TransactionId);

                Assert.Equal(2, keyManager.GetKeys(KeyState.Used, false).Count());
                Assert.Empty(keyManager.GetKeys(KeyState.Used, true));
                Assert.Equal(2, keyManager.GetKeys(KeyState.Used).Count());
                Assert.Empty(keyManager.GetKeys(KeyState.Locked, false));
                Assert.Equal(14, keyManager.GetKeys(KeyState.Locked, true).Count());
                Assert.Equal(14, keyManager.GetKeys(KeyState.Locked).Count());
                Assert.Equal(21, keyManager.GetKeys(KeyState.Clean, true).Count());
                Assert.Equal(21, keyManager.GetKeys(KeyState.Clean, false).Count());
                Assert.Equal(42, keyManager.GetKeys(KeyState.Clean).Count());
                Assert.Equal(58, keyManager.GetKeys().Count());

                Assert.Single(keyManager.GetKeys(KeyState.Used, false).Where(x => x.Label == "foo label"));
                Assert.Single(keyManager.GetKeys(KeyState.Used, false).Where(x => x.Label == "bar label"));

                // TEST MEMPOOL
                var txId5 = await rpc.SendToAddressAsync(key.GetP2wpkhAddress(network), Money.Coins(0.1m));

                await Task.Delay(1000);                 // Wait tx to arrive and get processed.

                Assert.NotEmpty(wallet.Coins.Where(x => x.TransactionId == txId5));
                var mempoolCoin = wallet.Coins.Single(x => x.TransactionId == txId5);
                Assert.Equal(Height.Mempool, mempoolCoin.Height);

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

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

                var res = await rpc.GetTxOutAsync(mempoolCoin.TransactionId, (int)mempoolCoin.Index, true);

                Assert.Equal(new Height(bitcoinStore.SmartHeaderChain.TipHeight), mempoolCoin.Height);
            }
            finally
            {
                wallet.NewFilterProcessed -= Common.Wallet_NewFilterProcessed;
                await wallet.StopAsync(CancellationToken.None);

                await synchronizer.StopAsync();

                await feeProvider.StopAsync(CancellationToken.None);

                nodes?.Dispose();
                node?.Disconnect();
            }
        }
예제 #23
0
        public async Task TestServicesAsync(string networkString)
        {
            await RuntimeParams.LoadAsync();

            var network          = Network.GetNetwork(networkString);
            var blocksToDownload = new List <uint256>();

            if (network == Network.Main)
            {
                blocksToDownload.Add(new uint256("00000000000000000037c2de35bd85f3e57f14ddd741ce6cee5b28e51473d5d0"));
                blocksToDownload.Add(new uint256("000000000000000000115315a43cb0cdfc4ea54a0e92bed127f4e395e718d8f9"));
                blocksToDownload.Add(new uint256("00000000000000000011b5b042ad0522b69aae36f7de796f563c895714bbd629"));
            }
            else if (network == Network.TestNet)
            {
                blocksToDownload.Add(new uint256("0000000097a664c4084b49faa6fd4417055cb8e5aac480abc31ddc57a8208524"));
                blocksToDownload.Add(new uint256("000000009ed5b82259ecd2aa4cd1f119db8da7a70e7ea78d9c9f603e01f93bcc"));
                blocksToDownload.Add(new uint256("00000000e6da8c2da304e9f5ad99c079df2c3803b49efded3061ecaf206ddc66"));
            }
            else
            {
                throw new NotSupportedNetworkException(network);
            }
            var dataDir = Path.Combine(Global.Instance.DataDir, EnvironmentHelpers.GetCallerFileName());

            BitcoinStore bitcoinStore = new BitcoinStore();
            await bitcoinStore.InitializeAsync(Path.Combine(dataDir, EnvironmentHelpers.GetMethodName()), network);

            var            addressManagerFolderPath = Path.Combine(dataDir, "AddressManager");
            var            addressManagerFilePath   = Path.Combine(addressManagerFolderPath, $"AddressManager{network}.dat");
            var            blocksFolderPath         = Path.Combine(dataDir, "Blocks", network.ToString());
            var            connectionParameters     = new NodeConnectionParameters();
            AddressManager addressManager           = null;

            try
            {
                addressManager = await NBitcoinHelpers.LoadAddressManagerFromPeerFileAsync(addressManagerFilePath);

                Logger.LogInfo($"Loaded {nameof(AddressManager)} from `{addressManagerFilePath}`.");
            }
            catch (DirectoryNotFoundException)
            {
                addressManager = new AddressManager();
            }
            catch (FileNotFoundException)
            {
                addressManager = new AddressManager();
            }
            catch (OverflowException)
            {
                File.Delete(addressManagerFilePath);
                addressManager = new AddressManager();
            }
            catch (FormatException)
            {
                File.Delete(addressManagerFilePath);
                addressManager = new AddressManager();
            }

            connectionParameters.TemplateBehaviors.Add(new AddressManagerBehavior(addressManager));
            connectionParameters.TemplateBehaviors.Add(bitcoinStore.CreateUntrustedP2pBehavior());

            using var nodes = new NodesGroup(network, connectionParameters, requirements: Constants.NodeRequirements);

            KeyManager           keyManager    = KeyManager.CreateNew(out _, "password");
            WasabiSynchronizer   syncer        = new WasabiSynchronizer(network, bitcoinStore, new Uri("http://localhost:12345"), Global.Instance.TorSocks5Endpoint);
            ServiceConfiguration serviceConfig = new ServiceConfiguration(50, 2, 21, 50, new IPEndPoint(IPAddress.Loopback, network.DefaultPort), Money.Coins(Constants.DefaultDustThreshold));
            CachedBlockProvider  blockProvider = new CachedBlockProvider(
                new P2pBlockProvider(nodes, null, syncer, serviceConfig, network),
                new FileSystemBlockRepository(blocksFolderPath, network));

            using Wallet wallet = Wallet.CreateAndRegisterServices(
                      network,
                      bitcoinStore,
                      keyManager,
                      syncer,
                      nodes,
                      dataDir,
                      new ServiceConfiguration(50, 2, 21, 50, new IPEndPoint(IPAddress.Loopback, network.DefaultPort), Money.Coins(Constants.DefaultDustThreshold)),
                      syncer,
                      blockProvider);
            Assert.True(Directory.Exists(blocksFolderPath));

            try
            {
                var mempoolTransactionAwaiter = new EventsAwaiter <SmartTransaction>(
                    h => bitcoinStore.MempoolService.TransactionReceived += h,
                    h => bitcoinStore.MempoolService.TransactionReceived -= h,
                    3);

                var nodeConnectionAwaiter = new EventsAwaiter <NodeEventArgs>(
                    h => nodes.ConnectedNodes.Added += h,
                    h => nodes.ConnectedNodes.Added -= h,
                    3);

                nodes.Connect();

                var downloadTasks = new List <Task <Block> >();
                using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(4));
                foreach (var hash in blocksToDownload)
                {
                    downloadTasks.Add(blockProvider.GetBlockAsync(hash, cts.Token));
                }

                await nodeConnectionAwaiter.WaitAsync(TimeSpan.FromMinutes(3));

                var i         = 0;
                var hashArray = blocksToDownload.ToArray();
                foreach (var block in await Task.WhenAll(downloadTasks))
                {
                    Assert.True(File.Exists(Path.Combine(blocksFolderPath, hashArray[i].ToString())));
                    i++;
                }

                await mempoolTransactionAwaiter.WaitAsync(TimeSpan.FromMinutes(1));
            }
            finally
            {
                // So next test will download the block.
                foreach (var hash in blocksToDownload)
                {
                    await blockProvider.BlockRepository.RemoveAsync(hash, CancellationToken.None);
                }
                if (wallet is { })
예제 #24
0
        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 { })
예제 #25
0
        public async Task TestServicesAsync(string networkString)
        {
            var network          = Network.GetNetwork(networkString);
            var blocksToDownload = new HashSet <uint256>();

            if (network == Network.Main)
            {
                blocksToDownload.Add(new uint256("00000000000000000037c2de35bd85f3e57f14ddd741ce6cee5b28e51473d5d0"));
                blocksToDownload.Add(new uint256("000000000000000000115315a43cb0cdfc4ea54a0e92bed127f4e395e718d8f9"));
                blocksToDownload.Add(new uint256("00000000000000000011b5b042ad0522b69aae36f7de796f563c895714bbd629"));
            }
            else if (network == Network.TestNet)
            {
                blocksToDownload.Add(new uint256("0000000097a664c4084b49faa6fd4417055cb8e5aac480abc31ddc57a8208524"));
                blocksToDownload.Add(new uint256("000000009ed5b82259ecd2aa4cd1f119db8da7a70e7ea78d9c9f603e01f93bcc"));
                blocksToDownload.Add(new uint256("00000000e6da8c2da304e9f5ad99c079df2c3803b49efded3061ecaf206ddc66"));
            }
            else
            {
                throw new NotSupportedException(network.ToString());
            }

            var            addressManagerFolderPath = Path.Combine(Global.Instance.DataDir, "AddressManager");
            var            addressManagerFilePath   = Path.Combine(addressManagerFolderPath, $"AddressManager{network}.dat");
            var            blocksFolderPath         = Path.Combine(Global.Instance.DataDir, "Blocks", network.ToString());
            var            connectionParameters     = new NodeConnectionParameters();
            AddressManager addressManager           = null;

            try
            {
                addressManager = AddressManager.LoadPeerFile(addressManagerFilePath);
                Logger.LogInfo <AddressManager>($"Loaded {nameof(AddressManager)} from `{addressManagerFilePath}`.");
            }
            catch (DirectoryNotFoundException ex)
            {
                Logger.LogInfo <AddressManager>($"{nameof(AddressManager)} did not exist at `{addressManagerFilePath}`. Initializing new one.");
                Logger.LogTrace <AddressManager>(ex);
                addressManager = new AddressManager();
            }
            catch (FileNotFoundException ex)
            {
                Logger.LogInfo <AddressManager>($"{nameof(AddressManager)} did not exist at `{addressManagerFilePath}`. Initializing new one.");
                Logger.LogTrace <AddressManager>(ex);
                addressManager = new AddressManager();
            }

            connectionParameters.TemplateBehaviors.Add(new AddressManagerBehavior(addressManager));
            var memPoolService = new MemPoolService();

            connectionParameters.TemplateBehaviors.Add(new MemPoolBehavior(memPoolService));

            var nodes = new NodesGroup(network, connectionParameters, requirements: Helpers.Constants.NodeRequirements);

            BitcoinStore bitcoinStore = new BitcoinStore();
            await bitcoinStore.InitializeAsync(Path.Combine(Global.Instance.DataDir, nameof(TestServicesAsync)), network);

            KeyManager         keyManager    = KeyManager.CreateNew(out _, "password");
            WasabiSynchronizer syncer        = new WasabiSynchronizer(network, bitcoinStore, new Uri("http://localhost:12345"), Global.Instance.TorSocks5Endpoint);
            WalletService      walletService = new WalletService(
                bitcoinStore,
                keyManager,
                syncer,
                new CcjClient(syncer, network, keyManager, new Uri("http://localhost:12345"), Global.Instance.TorSocks5Endpoint),
                memPoolService,
                nodes,
                Global.Instance.DataDir,
                new ServiceConfiguration(50, 2, 21, 50, new IPEndPoint(IPAddress.Loopback, network.DefaultPort), Money.Coins(0.0001m)));

            Assert.True(Directory.Exists(blocksFolderPath));

            try
            {
                nodes.ConnectedNodes.Added         += ConnectedNodes_Added;
                nodes.ConnectedNodes.Removed       += ConnectedNodes_Removed;
                memPoolService.TransactionReceived += MemPoolService_TransactionReceived;

                nodes.Connect();
                // Using the interlocked, not because it makes sense in this context, but to
                // set an example that these values are often concurrency sensitive
                var times = 0;
                while (Interlocked.Read(ref _nodeCount) < 3)
                {
                    if (times > 4200)                     // 7 minutes
                    {
                        throw new TimeoutException($"Connection test timed out.");
                    }
                    await Task.Delay(100);

                    times++;
                }

                times = 0;
                while (Interlocked.Read(ref _mempoolTransactionCount) < 3)
                {
                    if (times > 3000)                     // 3 minutes
                    {
                        throw new TimeoutException($"{nameof(MemPoolService)} test timed out.");
                    }
                    await Task.Delay(100);

                    times++;
                }

                foreach (var hash in blocksToDownload)
                {
                    using (var cts = new CancellationTokenSource(TimeSpan.FromMinutes(3)))
                    {
                        var block = await walletService.GetOrDownloadBlockAsync(hash, cts.Token);

                        Assert.True(File.Exists(Path.Combine(blocksFolderPath, hash.ToString())));
                        Logger.LogInfo <P2pTests>($"Full block is downloaded: {hash}.");
                    }
                }
            }
            finally
            {
                nodes.ConnectedNodes.Added         -= ConnectedNodes_Added;
                nodes.ConnectedNodes.Removed       -= ConnectedNodes_Removed;
                memPoolService.TransactionReceived -= MemPoolService_TransactionReceived;

                // So next test will download the block.
                foreach (var hash in blocksToDownload)
                {
                    await walletService?.DeleteBlockAsync(hash);
                }
                if (walletService != null)
                {
                    await walletService.StopAsync();
                }

                if (Directory.Exists(blocksFolderPath))
                {
                    Directory.Delete(blocksFolderPath, recursive: true);
                }

                IoHelpers.EnsureContainingDirectoryExists(addressManagerFilePath);
                addressManager?.SavePeerFile(addressManagerFilePath, network);
                Logger.LogInfo <P2pTests>($"Saved {nameof(AddressManager)} to `{addressManagerFilePath}`.");
                nodes?.Dispose();

                await syncer?.StopAsync();
            }
        }
예제 #26
0
    public async Task SpendUnconfirmedTxTestAsync()
    {
        (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.
        NodesGroup nodes = new(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.
        using HttpClientFactory httpClientFactory = new(torEndPoint : null, backendUriGetter : () => new Uri(RegTestFixture.BackendEndPoint));
        WasabiSynchronizer synchronizer = new(bitcoinStore, httpClientFactory);
        HybridFeeProvider  feeProvider  = new(synchronizer, null);

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

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

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

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

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

        // Get some money, make it confirm.
        var key = keyManager.GetNextReceiveKey("foo label", out _);

        try
        {
            nodes.Connect();                                                     // Start connection service.
            node.VersionHandshake();                                             // Start mempool service.
            synchronizer.Start(requestInterval: TimeSpan.FromSeconds(3), 10000); // Start wasabi synchronizer service.
            await feeProvider.StartAsync(CancellationToken.None);

            // 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);

            Assert.Empty(wallet.Coins);

            // Get some money, make it confirm.
            // this is necessary because we are in a fork now.
            var eventAwaiter = new EventAwaiter <ProcessedResult>(
                h => wallet.TransactionProcessor.WalletRelevantTransactionProcessed += h,
                h => wallet.TransactionProcessor.WalletRelevantTransactionProcessed -= h);
            var tx0Id = await rpc.SendToAddressAsync(
                key.GetP2wpkhAddress(network),
                Money.Coins(1m),
                replaceable : true);

            var eventArgs = await eventAwaiter.WaitAsync(TimeSpan.FromSeconds(21));

            Assert.Equal(tx0Id, eventArgs.NewlyReceivedCoins.Single().TransactionId);
            Assert.Single(wallet.Coins);

            TransactionBroadcaster broadcaster = new(network, bitcoinStore, httpClientFactory, walletManager);
            broadcaster.Initialize(nodes, rpc);

            var destination1 = key.PubKey.GetAddress(ScriptPubKeyType.Segwit, Network.Main);
            var destination2 = new Key().PubKey.GetAddress(ScriptPubKeyType.Legacy, Network.Main);
            var destination3 = new Key().PubKey.GetAddress(ScriptPubKeyType.Legacy, Network.Main);

            PaymentIntent operations = new(new DestinationRequest(destination1, Money.Coins(0.01m)), new DestinationRequest(destination2, Money.Coins(0.01m)), new DestinationRequest(destination3, Money.Coins(0.01m)));

            var tx1Res = wallet.BuildTransaction(password, operations, FeeStrategy.TwentyMinutesConfirmationTargetStrategy, allowUnconfirmed: true);
            Assert.Equal(2, tx1Res.InnerWalletOutputs.Count());
            Assert.Equal(2, tx1Res.OuterWalletOutputs.Count());

            // Spend the unconfirmed coin (send it to ourself)
            operations   = new PaymentIntent(key.PubKey.WitHash.ScriptPubKey, Money.Coins(0.5m));
            tx1Res       = wallet.BuildTransaction(password, operations, FeeStrategy.TwentyMinutesConfirmationTargetStrategy, allowUnconfirmed: true);
            eventAwaiter = new EventAwaiter <ProcessedResult>(
                h => wallet.TransactionProcessor.WalletRelevantTransactionProcessed += h,
                h => wallet.TransactionProcessor.WalletRelevantTransactionProcessed -= h);
            await broadcaster.SendTransactionAsync(tx1Res.Transaction);

            eventArgs = await eventAwaiter.WaitAsync(TimeSpan.FromSeconds(21));

            Assert.Equal(tx0Id, eventArgs.NewlySpentCoins.Single().TransactionId);
            Assert.Equal(tx1Res.Transaction.GetHash(), eventArgs.NewlyReceivedCoins.First().TransactionId);

            // There is a coin created by the latest spending transaction
            Assert.Contains(wallet.Coins, x => x.TransactionId == tx1Res.Transaction.GetHash());

            // There is a coin destroyed
            var allCoins = wallet.TransactionProcessor.Coins.AsAllCoinsView();
            Assert.Equal(1, allCoins.Count(x => !x.IsAvailable() && x.SpenderTransaction?.GetHash() == tx1Res.Transaction.GetHash()));

            // There is at least one coin created from the destruction of the first coin
            Assert.Contains(wallet.Coins, x => x.Transaction.Transaction.Inputs.Any(o => o.PrevOut.Hash == tx0Id));

            var totalWallet = wallet.Coins.Where(c => c.IsAvailable()).Sum(c => c.Amount);
            Assert.Equal((1 * Money.COIN) - tx1Res.Fee.Satoshi, totalWallet);

            // Spend the unconfirmed and unspent coin (send it to ourself)
            operations = new PaymentIntent(key.PubKey.WitHash.ScriptPubKey, Money.Coins(0.6m), subtractFee: true);
            var tx2Res = wallet.BuildTransaction(password, operations, FeeStrategy.TwentyMinutesConfirmationTargetStrategy, allowUnconfirmed: true);

            eventAwaiter = new EventAwaiter <ProcessedResult>(
                h => wallet.TransactionProcessor.WalletRelevantTransactionProcessed += h,
                h => wallet.TransactionProcessor.WalletRelevantTransactionProcessed -= h);
            await broadcaster.SendTransactionAsync(tx2Res.Transaction);

            eventArgs = await eventAwaiter.WaitAsync(TimeSpan.FromSeconds(21));

            var spentCoins = eventArgs.NewlySpentCoins.ToArray();
            Assert.Equal(tx1Res.Transaction.GetHash(), spentCoins.First().TransactionId);
            uint256 tx2Hash       = tx2Res.Transaction.GetHash();
            var     receivedCoins = eventArgs.NewlyReceivedCoins.ToArray();
            Assert.Equal(tx2Hash, receivedCoins[0].TransactionId);
            Assert.Equal(tx2Hash, receivedCoins[1].TransactionId);

            // There is a coin created by the latest spending transaction
            Assert.Contains(wallet.Coins, x => x.TransactionId == tx2Res.Transaction.GetHash());

            // There is a coin destroyed
            allCoins = wallet.TransactionProcessor.Coins.AsAllCoinsView();
            Assert.Equal(2, allCoins.Count(x => !x.IsAvailable() && x.SpenderTransaction?.GetHash() == tx2Hash));

            // There is at least one coin created from the destruction of the first coin
            Assert.Contains(wallet.Coins, x => x.Transaction.Transaction.Inputs.Any(o => o.PrevOut.Hash == tx1Res.Transaction.GetHash()));

            totalWallet = wallet.Coins.Where(c => c.IsAvailable()).Sum(c => c.Amount);
            Assert.Equal((1 * Money.COIN) - tx1Res.Fee.Satoshi - tx2Res.Fee.Satoshi, totalWallet);

            Interlocked.Exchange(ref Common.FiltersProcessedByWalletCount, 0);
            var blockId = (await rpc.GenerateAsync(1)).Single();
            try
            {
                await Common.WaitForFiltersToBeProcessedAsync(TimeSpan.FromSeconds(120), 1);
            }
            catch (TimeoutException)
            {
                Logger.LogInfo("Index was not processed.");
                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.
            }

            // Verify transactions are confirmed in the blockchain
            var block = await rpc.GetBlockAsync(blockId);

            Assert.Contains(block.Transactions, x => x.GetHash() == tx2Res.Transaction.GetHash());
            Assert.Contains(block.Transactions, x => x.GetHash() == tx1Res.Transaction.GetHash());
            Assert.Contains(block.Transactions, x => x.GetHash() == tx0Id);

            Assert.True(wallet.Coins.All(x => x.Confirmed));

            // Test coin basic count.
            ICoinsView GetAllCoins() => wallet.TransactionProcessor.Coins.AsAllCoinsView();

            var coinCount = GetAllCoins().Count();
            var to        = keyManager.GetNextReceiveKey("foo", out _);
            var res       = wallet.BuildTransaction(password, new PaymentIntent(to.P2wpkhScript, Money.Coins(0.2345m), label: "bar"), FeeStrategy.TwentyMinutesConfirmationTargetStrategy, allowUnconfirmed: true);
            await broadcaster.SendTransactionAsync(res.Transaction);

            Assert.Equal(coinCount + 2, GetAllCoins().Count());
            Assert.Equal(2, GetAllCoins().Count(x => !x.Confirmed));
            Interlocked.Exchange(ref Common.FiltersProcessedByWalletCount, 0);
            await rpc.GenerateAsync(1);

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

            Assert.Equal(coinCount + 2, GetAllCoins().Count());
            Assert.Equal(0, GetAllCoins().Count(x => !x.Confirmed));
        }
        finally
        {
            bitcoinStore.IndexStore.NewFilter -= Common.Wallet_NewFilterProcessed;
            await walletManager.RemoveAndStopAllAsync(CancellationToken.None);

            await synchronizer.StopAsync();

            await feeProvider.StopAsync(CancellationToken.None);

            nodes?.Dispose();
            node?.Disconnect();
        }
    }
예제 #27
0
        public async Task BuildTransactionValidationsTestAsync()
        {
            (string password, IRPCClient rpc, Network network, Coordinator coordinator, ServiceConfiguration serviceConfiguration, BitcoinStore bitcoinStore, Backend.Global global) = await Common.InitializeTestEnvironmentAsync(RegTestFixture, 1);

            // 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          = Common.GetWorkDir();
            var blocksFolderPath = Path.Combine(workDir, "Blocks", network.ToString());
            CachedBlockProvider blockProvider = new CachedBlockProvider(
                new P2pBlockProvider(nodes, null, synchronizer, serviceConfiguration, network),
                new FileSystemBlockRepository(blocksFolderPath, network));

            using var wallet           = Wallet.CreateAndRegisterServices(network, bitcoinStore, keyManager, synchronizer, nodes, workDir, serviceConfiguration, synchronizer, blockProvider);
            wallet.NewFilterProcessed += Common.Wallet_NewFilterProcessed;

            var scp = new Key().ScriptPubKey;

            var validIntent   = new PaymentIntent(scp, Money.Coins(1));
            var invalidIntent = new PaymentIntent(
                new DestinationRequest(scp, Money.Coins(10 * 1000 * 1000)),
                new DestinationRequest(scp, Money.Coins(12 * 1000 * 1000)));

            Assert.Throws <OverflowException>(() => new PaymentIntent(
                                                  new DestinationRequest(scp, Money.Satoshis(long.MaxValue)),
                                                  new DestinationRequest(scp, Money.Satoshis(long.MaxValue)),
                                                  new DestinationRequest(scp, Money.Satoshis(5))));

            Logger.TurnOff();
            Assert.Throws <ArgumentNullException>(() => wallet.BuildTransaction(null, null, FeeStrategy.CreateFromConfirmationTarget(4)));

            // toSend cannot have a null element
            Assert.Throws <ArgumentNullException>(() => wallet.BuildTransaction(null, new PaymentIntent(new[] { (DestinationRequest)null }), FeeStrategy.CreateFromConfirmationTarget(0)));

            // toSend cannot have a zero element
            Assert.Throws <ArgumentException>(() => wallet.BuildTransaction(null, new PaymentIntent(Array.Empty <DestinationRequest>()), FeeStrategy.SevenDaysConfirmationTargetStrategy));

            // feeTarget has to be in the range 0 to 1008
            Assert.Throws <ArgumentOutOfRangeException>(() => wallet.BuildTransaction(null, validIntent, FeeStrategy.CreateFromConfirmationTarget(-10)));
            Assert.Throws <ArgumentOutOfRangeException>(() => wallet.BuildTransaction(null, validIntent, FeeStrategy.CreateFromConfirmationTarget(2000)));

            // toSend amount sum has to be in range 0 to 2099999997690000
            Assert.Throws <ArgumentOutOfRangeException>(() => wallet.BuildTransaction(null, invalidIntent, FeeStrategy.TwentyMinutesConfirmationTargetStrategy));

            // toSend negative sum amount
            Assert.Throws <ArgumentOutOfRangeException>(() => wallet.BuildTransaction(null, new PaymentIntent(scp, Money.Satoshis(-10000)), FeeStrategy.TwentyMinutesConfirmationTargetStrategy));

            // toSend negative operation amount
            Assert.Throws <ArgumentOutOfRangeException>(() => wallet.BuildTransaction(
                                                            null,
                                                            new PaymentIntent(
                                                                new DestinationRequest(scp, Money.Satoshis(20000)),
                                                                new DestinationRequest(scp, Money.Satoshis(-10000))),
                                                            FeeStrategy.TwentyMinutesConfirmationTargetStrategy));

            // allowedInputs cannot be empty
            Assert.Throws <ArgumentException>(() => wallet.BuildTransaction(null, validIntent, FeeStrategy.TwentyMinutesConfirmationTargetStrategy, allowedInputs: Array.Empty <OutPoint>()));

            // "Only one element can contain the AllRemaining flag.
            Assert.Throws <ArgumentException>(() => wallet.BuildTransaction(
                                                  password,
                                                  new PaymentIntent(
                                                      new DestinationRequest(scp, MoneyRequest.CreateAllRemaining(), "zero"),
                                                      new DestinationRequest(scp, MoneyRequest.CreateAllRemaining(), "zero")),
                                                  FeeStrategy.SevenDaysConfirmationTargetStrategy,
                                                  false));

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

            // Generate some coins
            await rpc.GenerateAsync(2);

            try
            {
                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);

                using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30)))
                {
                    await wallet.StartAsync(cts.Token);                     // Initialize wallet service.
                }

                // subtract Fee from amount index with no enough money
                var operations = new PaymentIntent(
                    new DestinationRequest(scp, Money.Coins(1m), subtractFee: true),
                    new DestinationRequest(scp, Money.Coins(0.5m)));
                Assert.Throws <InsufficientBalanceException>(() => wallet.BuildTransaction(password, operations, FeeStrategy.TwentyMinutesConfirmationTargetStrategy, false));

                // No enough money (only one confirmed coin, no unconfirmed allowed)
                operations = new PaymentIntent(scp, Money.Coins(1.5m));
                Assert.Throws <InsufficientBalanceException>(() => wallet.BuildTransaction(null, operations, FeeStrategy.TwentyMinutesConfirmationTargetStrategy));

                // No enough money (only one confirmed coin, unconfirmed allowed)
                Assert.Throws <InsufficientBalanceException>(() => wallet.BuildTransaction(null, operations, FeeStrategy.TwentyMinutesConfirmationTargetStrategy, true));

                // Add new money with no confirmation
                var txId2 = await rpc.SendToAddressAsync(keyManager.GetNextReceiveKey("bar", out _).GetP2wpkhAddress(network), Money.Coins(2m));

                await Task.Delay(1000);                 // Wait tx to arrive and get processed.

                // Enough money (one confirmed coin and one unconfirmed coin, unconfirmed are NOT allowed)
                Assert.Throws <InsufficientBalanceException>(() => wallet.BuildTransaction(null, operations, FeeStrategy.TwentyMinutesConfirmationTargetStrategy, false));

                // Enough money (one unconfirmed coin, unconfirmed are allowed)
                var btx       = wallet.BuildTransaction(password, operations, FeeStrategy.TwentyMinutesConfirmationTargetStrategy, true);
                var spentCoin = Assert.Single(btx.SpentCoins);
                Assert.False(spentCoin.Confirmed);

                // Enough money (one confirmed coin and one unconfirmed coin, unconfirmed are allowed)
                operations = new PaymentIntent(scp, Money.Coins(2.5m));
                btx        = wallet.BuildTransaction(password, operations, FeeStrategy.TwentyMinutesConfirmationTargetStrategy, true);
                Assert.Equal(2, btx.SpentCoins.Count());
                Assert.Equal(1, btx.SpentCoins.Count(c => c.Confirmed));
                Assert.Equal(1, btx.SpentCoins.Count(c => !c.Confirmed));

                // Only one operation with AllRemainingFlag

                Assert.Throws <ArgumentException>(() => wallet.BuildTransaction(
                                                      null,
                                                      new PaymentIntent(
                                                          new DestinationRequest(scp, MoneyRequest.CreateAllRemaining()),
                                                          new DestinationRequest(scp, MoneyRequest.CreateAllRemaining())),
                                                      FeeStrategy.TwentyMinutesConfirmationTargetStrategy));

                Logger.TurnOn();

                operations = new PaymentIntent(scp, Money.Coins(0.5m));
                btx        = wallet.BuildTransaction(password, operations, FeeStrategy.TwentyMinutesConfirmationTargetStrategy);
            }
            finally
            {
                await wallet.StopAsync(CancellationToken.None);

                // Dispose wasabi synchronizer service.
                if (synchronizer is { })
예제 #28
0
    public async Task BuildTransactionReorgsTestAsync()
    {
        (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.
        NodesGroup nodes = new(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.
        using HttpClientFactory httpClientFactory = new(torEndPoint : null, backendUriGetter : () => new Uri(RegTestFixture.BackendEndPoint));
        WasabiSynchronizer synchronizer = new(bitcoinStore, httpClientFactory);
        HybridFeeProvider  feeProvider  = new(synchronizer, null);

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

        // 5. Create wallet service.
        var workDir = Helpers.Common.GetWorkDir();
        CachedBlockProvider blockProvider = new(
            new P2pBlockProvider(nodes, null, httpClientFactory, serviceConfiguration, network),
            bitcoinStore.BlockRepository);
        WalletManager walletManager = new(network, workDir, new WalletDirectories(network, workDir));

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

        var baseTip = await rpc.GetBestBlockHashAsync();

        // Generate script
        using var k = new Key();
        var scp = k.PubKey.GetAddress(ScriptPubKeyType.Legacy, Network.Main);

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

        // Generate some coins
        await rpc.GenerateAsync(2);

        try
        {
            nodes.Connect();                                                     // Start connection service.
            node.VersionHandshake();                                             // Start mempool service.
            synchronizer.Start(requestInterval: TimeSpan.FromSeconds(3), 10000); // Start wasabi synchronizer service.
            await feeProvider.StartAsync(CancellationToken.None);

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

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

            using var wallet = await walletManager.AddAndStartWalletAsync(keyManager);

            var coin = Assert.Single(wallet.Coins);
            Assert.True(coin.Confirmed);
            TransactionBroadcaster broadcaster = new(network, bitcoinStore, httpClientFactory, walletManager);
            broadcaster.Initialize(nodes, rpc);

            // Send money before reorg.
            PaymentIntent operations = new(scp, Money.Coins(0.011m));
            var           btx1       = wallet.BuildTransaction(password, operations, FeeStrategy.TwentyMinutesConfirmationTargetStrategy);
            await broadcaster.SendTransactionAsync(btx1.Transaction);

            var coin2 = Assert.Single(wallet.Coins);
            Assert.NotEqual(coin, coin2);
            Assert.False(coin2.Confirmed);

            operations = new PaymentIntent(scp, Money.Coins(0.012m));
            var btx2 = wallet.BuildTransaction(password, operations, FeeStrategy.TwentyMinutesConfirmationTargetStrategy, allowUnconfirmed: true);
            await broadcaster.SendTransactionAsync(btx2.Transaction);

            var coin3 = Assert.Single(wallet.Coins);
            Assert.NotEqual(coin2, coin3);
            Assert.False(coin3.Confirmed);

            // Test synchronization after fork.
            // Invalidate the blocks containing the funding transaction
            var tip = await rpc.GetBestBlockHashAsync();

            await rpc.InvalidateBlockAsync(tip);             // Reorg 1

            tip = await rpc.GetBestBlockHashAsync();

            await rpc.InvalidateBlockAsync(tip);             // Reorg 2

            // Generate three new blocks (replace the previous invalidated ones)
            Interlocked.Exchange(ref Common.FiltersProcessedByWalletCount, 0);
            await rpc.GenerateAsync(3);

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

            await Task.Delay(100);             // Wait for tx processing.

            var coin4 = Assert.Single(wallet.Coins);
            Assert.Equal(coin3, coin4);
            Assert.True(coin.Confirmed);
            Assert.True(coin2.Confirmed);
            Assert.True(coin3.Confirmed);
            Assert.True(coin4.Confirmed);

            // Send money after reorg.
            // When we invalidate a block, the transactions set in the invalidated block
            // are reintroduced when we generate a new block through the rpc call
            operations = new PaymentIntent(scp, Money.Coins(0.013m));
            var btx3 = wallet.BuildTransaction(password, operations, FeeStrategy.TwentyMinutesConfirmationTargetStrategy);
            await broadcaster.SendTransactionAsync(btx3.Transaction);

            var coin5 = Assert.Single(wallet.Coins);
            Assert.NotEqual(coin4, coin5);
            Assert.False(coin5.Confirmed);

            operations = new PaymentIntent(scp, Money.Coins(0.014m));
            var btx4 = wallet.BuildTransaction(password, operations, FeeStrategy.TwentyMinutesConfirmationTargetStrategy, allowUnconfirmed: true);
            await broadcaster.SendTransactionAsync(btx4.Transaction);

            var coin6 = Assert.Single(wallet.Coins);
            Assert.NotEqual(coin5, coin6);
            Assert.False(coin6.Confirmed);

            // Test synchronization after fork with different transactions.
            // Create a fork that invalidates the blocks containing the funding transaction
            Interlocked.Exchange(ref Common.FiltersProcessedByWalletCount, 0);
            await rpc.InvalidateBlockAsync(baseTip);

            try
            {
                await rpc.AbandonTransactionAsync(fundingTxId);
            }
            catch
            {
                if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
                {
                    throw;
                }
                return;                 // Occassionally this fails on Linux or OSX, I have no idea why.
            }
            // Spend the inputs of the tx so we know
            var success = bitcoinStore.TransactionStore.TryGetTransaction(fundingTxId, out var invalidSmartTransaction);
            Assert.True(success);
            var invalidCoin = Assert.Single(((CoinsRegistry)wallet.Coins).AsAllCoinsView().CreatedBy(invalidSmartTransaction !.GetHash()));
            Assert.NotNull(invalidCoin.SpenderTransaction);
            Assert.True(invalidCoin.Confirmed);

            var overwriteTx = Transaction.Create(network);
            overwriteTx.Inputs.AddRange(invalidSmartTransaction.Transaction.Inputs);
            var  walletAddress = keyManager.GetNextReceiveKey("foo", out _).GetP2wpkhAddress(network);
            bool onAddress     = false;
            foreach (var invalidOutput in invalidSmartTransaction.Transaction.Outputs)
            {
                if (onAddress)
                {
                    overwriteTx.Outputs.Add(new TxOut(invalidOutput.Value, new Key().GetAddress(ScriptPubKeyType.Segwit, network)));
                }
                else
                {
                    overwriteTx.Outputs.Add(new TxOut(invalidOutput.Value, walletAddress));
                    onAddress = true;
                }
            }
            var srtxwwreq = new SignRawTransactionRequest();
            srtxwwreq.Transaction = overwriteTx;
            var srtxwwres = await rpc.SignRawTransactionWithWalletAsync(srtxwwreq);

            var eventAwaiter = new EventAwaiter <ProcessedResult>(
                h => wallet.TransactionProcessor.WalletRelevantTransactionProcessed += h,
                h => wallet.TransactionProcessor.WalletRelevantTransactionProcessed -= h);
            await rpc.SendRawTransactionAsync(srtxwwres.SignedTransaction);

            await rpc.GenerateAsync(10);

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

            var eventArgs = await eventAwaiter.WaitAsync(TimeSpan.FromSeconds(21));

            var doubleSpend = Assert.Single(eventArgs.SuccessfullyDoubleSpentCoins);
            Assert.Equal(invalidCoin.TransactionId, doubleSpend.TransactionId);

            var curBlockHash = await rpc.GetBestBlockHashAsync();

            blockCount = await rpc.GetBlockCountAsync();

            Assert.Equal(bitcoinStore.SmartHeaderChain.TipHash, curBlockHash);
            Assert.Equal((int)bitcoinStore.SmartHeaderChain.TipHeight, blockCount);

            // Make sure the funding transaction is not in any block of the chain
            while (curBlockHash != rpc.Network.GenesisHash)
            {
                var block = await rpc.GetBlockAsync(curBlockHash);

                if (block.Transactions.Any(tx => tx.GetHash() == fundingTxId))
                {
                    throw new Exception($"Transaction found in block at height {blockCount} hash: {block.GetHash()}");
                }
                curBlockHash = block.Header.HashPrevBlock;
                blockCount--;
            }

            // Get some money, make it confirm.
            // this is necessary because we are in a fork now.
            eventAwaiter = new EventAwaiter <ProcessedResult>(
                h => wallet.TransactionProcessor.WalletRelevantTransactionProcessed += h,
                h => wallet.TransactionProcessor.WalletRelevantTransactionProcessed -= h);
            fundingTxId = await rpc.SendToAddressAsync(key.GetP2wpkhAddress(network), Money.Coins(1m), replaceable : true);

            eventArgs = await eventAwaiter.WaitAsync(TimeSpan.FromSeconds(21));

            Assert.Equal(fundingTxId, eventArgs.NewlyReceivedCoins.Single().TransactionId);
            Assert.Contains(fundingTxId, wallet.Coins.Select(x => x.TransactionId));

            var fundingBumpTxId = await rpc.BumpFeeAsync(fundingTxId);

            await Task.Delay(2000);             // Waits for the funding transaction get to the mempool.

            Assert.Contains(fundingBumpTxId.TransactionId, wallet.Coins.Select(x => x.TransactionId));
            Assert.DoesNotContain(fundingTxId, wallet.Coins.Select(x => x.TransactionId));
            Assert.Single(wallet.Coins.Where(x => x.TransactionId == fundingBumpTxId.TransactionId));

            // Confirm the coin
            Interlocked.Exchange(ref Common.FiltersProcessedByWalletCount, 0);
            await rpc.GenerateAsync(1);

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

            Assert.Single(wallet.Coins.Where(x => x.Confirmed && x.TransactionId == fundingBumpTxId.TransactionId));
        }
        finally
        {
            bitcoinStore.IndexStore.NewFilter -= Common.Wallet_NewFilterProcessed;
            await walletManager.RemoveAndStopAllAsync(CancellationToken.None);

            await synchronizer.StopAsync();

            await feeProvider.StopAsync(CancellationToken.None);

            nodes?.Dispose();
            node?.Disconnect();
        }
    }
예제 #29
0
    public async Task ReplaceByFeeTxTestAsync()
    {
        (string password, IRPCClient rpc, Network network, _, ServiceConfiguration serviceConfiguration, BitcoinStore bitcoinStore, Backend.Global global) = await Common.InitializeTestEnvironmentAsync(RegTestFixture, 1);

        // Create the services.
        // 1. Create connection service.
        NodesGroup nodes = new(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.
        using HttpClientFactory httpClientFactory = new(torEndPoint : null, backendUriGetter : () => new Uri(RegTestFixture.BackendEndPoint));
        WasabiSynchronizer synchronizer = new(bitcoinStore, httpClientFactory);
        HybridFeeProvider  feeProvider  = new(synchronizer, null);

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

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

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

        using var wallet           = Wallet.CreateAndRegisterServices(network, bitcoinStore, keyManager, synchronizer, workDir, serviceConfiguration, feeProvider, blockProvider);
        wallet.NewFilterProcessed += Common.Wallet_NewFilterProcessed;

        Assert.Empty(wallet.Coins);

        // Get some money, make it confirm.
        var key = keyManager.GetNextReceiveKey("foo label", out _);

        try
        {
            nodes.Connect();                                                     // Start connection service.
            node.VersionHandshake();                                             // Start mempool service.
            synchronizer.Start(requestInterval: TimeSpan.FromSeconds(3), 10000); // Start wasabi synchronizer service.
            await feeProvider.StartAsync(CancellationToken.None);

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

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

            using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30)))
            {
                await wallet.StartAsync(cts.Token);                 // Initialize wallet service.
            }

            Assert.Empty(wallet.Coins);

            var tx0Id = await rpc.SendToAddressAsync(key.GetP2wpkhAddress(network), Money.Coins(1m), replaceable : true);

            while (!wallet.Coins.Any())
            {
                await Task.Delay(500);                 // Waits for the funding transaction get to the mempool.
            }

            Assert.Single(wallet.Coins);
            Assert.True(wallet.Coins.First().IsReplaceable());

            var bfr = await rpc.BumpFeeAsync(tx0Id);

            var tx1Id = bfr.TransactionId;
            await Task.Delay(2000);             // Waits for the replacement transaction get to the mempool.

            Assert.Single(wallet.Coins);
            Assert.True(wallet.Coins.First().IsReplaceable());
            Assert.Equal(tx1Id, wallet.Coins.First().TransactionId);

            bfr = await rpc.BumpFeeAsync(tx1Id);

            var tx2Id = bfr.TransactionId;
            await Task.Delay(2000);             // Waits for the replacement transaction get to the mempool.

            Assert.Single(wallet.Coins);
            Assert.True(wallet.Coins.First().IsReplaceable());
            Assert.Equal(tx2Id, wallet.Coins.First().TransactionId);

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

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

            var coin = Assert.Single(wallet.Coins);
            Assert.True(coin.Confirmed);
            Assert.False(coin.IsReplaceable());
            Assert.Equal(tx2Id, coin.TransactionId);
        }
        finally
        {
            await wallet.StopAsync(CancellationToken.None);

            await synchronizer.StopAsync();

            await feeProvider.StopAsync(CancellationToken.None);

            nodes?.Dispose();
            node?.Disconnect();
        }
    }