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()); } }
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)); }
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); }
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); }
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()); } }
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 _)); }
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>())); }
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()); }
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); }
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); }
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); }
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); }
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); }
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(); } }
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(); } }
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 { })
public async Task SendTestsAsync() { (string password, IRPCClient rpc, Network network, _, ServiceConfiguration serviceConfiguration, BitcoinStore bitcoinStore, Backend.Global global) = await Common.InitializeTestEnvironmentAsync(RegTestFixture, 1); bitcoinStore.IndexStore.NewFilter += Common.Wallet_NewFilterProcessed; // Create the services. // 1. Create connection service. var nodes = new NodesGroup(global.Config.Network, requirements: Constants.NodeRequirements); nodes.ConnectedNodes.Add(await RegTestFixture.BackendRegTestNode.CreateNewP2pNodeAsync()); // 2. Create mempool service. Node node = await RegTestFixture.BackendRegTestNode.CreateNewP2pNodeAsync(); node.Behaviors.Add(bitcoinStore.CreateUntrustedP2pBehavior()); // 3. Create wasabi synchronizer service. var synchronizer = new WasabiSynchronizer(rpc.Network, bitcoinStore, new Uri(RegTestFixture.BackendEndPoint), null); // 4. Create key manager service. var keyManager = KeyManager.CreateNew(out _, password); // 5. Create wallet service. var workDir = Tests.Common.GetWorkDir(); CachedBlockProvider blockProvider = new CachedBlockProvider( new P2pBlockProvider(nodes, null, synchronizer, serviceConfiguration, network), bitcoinStore.BlockRepository); var walletManager = new WalletManager(network, new WalletDirectories(workDir)); walletManager.RegisterServices(bitcoinStore, synchronizer, nodes, serviceConfiguration, synchronizer, blockProvider); // Get some money, make it confirm. var key = keyManager.GetNextReceiveKey("foo label", out _); var key2 = keyManager.GetNextReceiveKey("foo label", out _); var txId = await rpc.SendToAddressAsync(key.GetP2wpkhAddress(network), Money.Coins(1m)); Assert.NotNull(txId); await rpc.GenerateAsync(1); var txId2 = await rpc.SendToAddressAsync(key2.GetP2wpkhAddress(network), Money.Coins(1m)); Assert.NotNull(txId2); await rpc.GenerateAsync(1); try { Interlocked.Exchange(ref Common.FiltersProcessedByWalletCount, 0); nodes.Connect(); // Start connection service. node.VersionHandshake(); // Start mempool service. synchronizer.Start(requestInterval: TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(5), 10000); // Start wasabi synchronizer service. // Wait until the filter our previous transaction is present. var blockCount = await rpc.GetBlockCountAsync(); await Common.WaitForFiltersToBeProcessedAsync(TimeSpan.FromSeconds(120), blockCount); var wallet = await walletManager.AddAndStartWalletAsync(keyManager); var broadcaster = new TransactionBroadcaster(network, bitcoinStore, synchronizer, nodes, walletManager, rpc); var waitCount = 0; while (wallet.Coins.Sum(x => x.Amount) == Money.Zero) { await Task.Delay(1000); waitCount++; if (waitCount >= 21) { Logger.LogInfo("Funding transaction to the wallet did not arrive."); return; // Very rarely this test fails. I have no clue why. Probably because all these RegTests are interconnected, anyway let's not bother the CI with it. } } var scp = new Key().ScriptPubKey; var res2 = wallet.BuildTransaction(password, new PaymentIntent(scp, Money.Coins(0.05m), label: "foo"), FeeStrategy.CreateFromConfirmationTarget(5), allowUnconfirmed: false); Assert.NotNull(res2.Transaction); Assert.Single(res2.OuterWalletOutputs); Assert.Equal(scp, res2.OuterWalletOutputs.Single().ScriptPubKey); Assert.Single(res2.InnerWalletOutputs); Assert.True(res2.Fee > Money.Satoshis(2 * 100)); // since there is a sanity check of 2sat/vb in the server Assert.InRange(res2.FeePercentOfSent, 0, 1); Assert.Single(res2.SpentCoins); var spentCoin = Assert.Single(res2.SpentCoins); Assert.Contains(new[] { key.P2wpkhScript, key2.P2wpkhScript }, x => x == spentCoin.ScriptPubKey); Assert.Equal(Money.Coins(1m), res2.SpentCoins.Single().Amount); Assert.False(res2.SpendsUnconfirmed); await broadcaster.SendTransactionAsync(res2.Transaction); Assert.Contains(res2.InnerWalletOutputs.Single(), wallet.Coins); #region Basic Script receive = keyManager.GetNextReceiveKey("Basic", out _).P2wpkhScript; Money amountToSend = wallet.Coins.Where(x => !x.Unavailable).Sum(x => x.Amount) / 2; var res = wallet.BuildTransaction(password, new PaymentIntent(receive, amountToSend, label: "foo"), FeeStrategy.SevenDaysConfirmationTargetStrategy, allowUnconfirmed: true); foreach (SmartCoin coin in res.SpentCoins) { Assert.False(coin.CoinJoinInProgress); Assert.True(coin.Confirmed); Assert.Null(coin.SpenderTransactionId); Assert.True(coin.Unspent); } Assert.Equal(2, res.InnerWalletOutputs.Count()); Assert.Empty(res.OuterWalletOutputs); var activeOutput = res.InnerWalletOutputs.Single(x => x.ScriptPubKey == receive); var changeOutput = res.InnerWalletOutputs.Single(x => x.ScriptPubKey != receive); Assert.Equal(receive, activeOutput.ScriptPubKey); Assert.Equal(amountToSend, activeOutput.Amount); if (res.SpentCoins.Sum(x => x.Amount) - activeOutput.Amount == res.Fee) // this happens when change is too small { Assert.Contains(res.Transaction.Transaction.Outputs, x => x.Value == activeOutput.Amount); Logger.LogInfo($"Change Output: {changeOutput.Amount.ToString(false, true)} {changeOutput.ScriptPubKey.GetDestinationAddress(network)}"); } Logger.LogInfo($"{nameof(res.Fee)}: {res.Fee}"); Logger.LogInfo($"{nameof(res.FeePercentOfSent)}: {res.FeePercentOfSent} %"); Logger.LogInfo($"{nameof(res.SpendsUnconfirmed)}: {res.SpendsUnconfirmed}"); Logger.LogInfo($"Active Output: {activeOutput.Amount.ToString(false, true)} {activeOutput.ScriptPubKey.GetDestinationAddress(network)}"); Logger.LogInfo($"TxId: {res.Transaction.GetHash()}"); var foundReceive = false; Assert.InRange(res.Transaction.Transaction.Outputs.Count, 1, 2); foreach (var output in res.Transaction.Transaction.Outputs) { if (output.ScriptPubKey == receive) { foundReceive = true; Assert.Equal(amountToSend, output.Value); } } Assert.True(foundReceive); await broadcaster.SendTransactionAsync(res.Transaction); #endregion Basic #region SubtractFeeFromAmount receive = keyManager.GetNextReceiveKey("SubtractFeeFromAmount", out _).P2wpkhScript; amountToSend = wallet.Coins.Where(x => !x.Unavailable).Sum(x => x.Amount) / 3; res = wallet.BuildTransaction(password, new PaymentIntent(receive, amountToSend, subtractFee: true, label: "foo"), FeeStrategy.SevenDaysConfirmationTargetStrategy, allowUnconfirmed: true); Assert.Equal(2, res.InnerWalletOutputs.Count()); Assert.Empty(res.OuterWalletOutputs); activeOutput = res.InnerWalletOutputs.Single(x => x.ScriptPubKey == receive); changeOutput = res.InnerWalletOutputs.Single(x => x.ScriptPubKey != receive); Assert.Equal(receive, activeOutput.ScriptPubKey); Assert.Equal(amountToSend - res.Fee, activeOutput.Amount); Assert.Contains(res.Transaction.Transaction.Outputs, x => x.Value == changeOutput.Amount); Logger.LogInfo($"{nameof(res.Fee)}: {res.Fee}"); Logger.LogInfo($"{nameof(res.FeePercentOfSent)}: {res.FeePercentOfSent} %"); Logger.LogInfo($"{nameof(res.SpendsUnconfirmed)}: {res.SpendsUnconfirmed}"); Logger.LogInfo($"Active Output: {activeOutput.Amount.ToString(false, true)} {activeOutput.ScriptPubKey.GetDestinationAddress(network)}"); Logger.LogInfo($"Change Output: {changeOutput.Amount.ToString(false, true)} {changeOutput.ScriptPubKey.GetDestinationAddress(network)}"); Logger.LogInfo($"TxId: {res.Transaction.GetHash()}"); foundReceive = false; Assert.InRange(res.Transaction.Transaction.Outputs.Count, 1, 2); foreach (var output in res.Transaction.Transaction.Outputs) { if (output.ScriptPubKey == receive) { foundReceive = true; Assert.Equal(amountToSend - res.Fee, output.Value); } } Assert.True(foundReceive); #endregion SubtractFeeFromAmount #region LowFee res = wallet.BuildTransaction(password, new PaymentIntent(receive, amountToSend, label: "foo"), FeeStrategy.SevenDaysConfirmationTargetStrategy, allowUnconfirmed: true); Assert.Equal(2, res.InnerWalletOutputs.Count()); Assert.Empty(res.OuterWalletOutputs); activeOutput = res.InnerWalletOutputs.Single(x => x.ScriptPubKey == receive); changeOutput = res.InnerWalletOutputs.Single(x => x.ScriptPubKey != receive); Assert.Equal(receive, activeOutput.ScriptPubKey); Assert.Equal(amountToSend, activeOutput.Amount); Assert.Contains(res.Transaction.Transaction.Outputs, x => x.Value == changeOutput.Amount); Logger.LogInfo($"{nameof(res.Fee)}: {res.Fee}"); Logger.LogInfo($"{nameof(res.FeePercentOfSent)}: {res.FeePercentOfSent} %"); Logger.LogInfo($"{nameof(res.SpendsUnconfirmed)}: {res.SpendsUnconfirmed}"); Logger.LogInfo($"Active Output: {activeOutput.Amount.ToString(false, true)} {activeOutput.ScriptPubKey.GetDestinationAddress(network)}"); Logger.LogInfo($"Change Output: {changeOutput.Amount.ToString(false, true)} {changeOutput.ScriptPubKey.GetDestinationAddress(network)}"); Logger.LogInfo($"TxId: {res.Transaction.GetHash()}"); foundReceive = false; Assert.InRange(res.Transaction.Transaction.Outputs.Count, 1, 2); foreach (var output in res.Transaction.Transaction.Outputs) { if (output.ScriptPubKey == receive) { foundReceive = true; Assert.Equal(amountToSend, output.Value); } } Assert.True(foundReceive); #endregion LowFee #region MediumFee res = wallet.BuildTransaction(password, new PaymentIntent(receive, amountToSend, label: "foo"), FeeStrategy.OneDayConfirmationTargetStrategy, allowUnconfirmed: true); Assert.Equal(2, res.InnerWalletOutputs.Count()); Assert.Empty(res.OuterWalletOutputs); activeOutput = res.InnerWalletOutputs.Single(x => x.ScriptPubKey == receive); changeOutput = res.InnerWalletOutputs.Single(x => x.ScriptPubKey != receive); Assert.Equal(receive, activeOutput.ScriptPubKey); Assert.Equal(amountToSend, activeOutput.Amount); Assert.Contains(res.Transaction.Transaction.Outputs, x => x.Value == changeOutput.Amount); Logger.LogInfo($"{nameof(res.Fee)}: {res.Fee}"); Logger.LogInfo($"{nameof(res.FeePercentOfSent)}: {res.FeePercentOfSent} %"); Logger.LogInfo($"{nameof(res.SpendsUnconfirmed)}: {res.SpendsUnconfirmed}"); Logger.LogInfo($"Active Output: {activeOutput.Amount.ToString(false, true)} {activeOutput.ScriptPubKey.GetDestinationAddress(network)}"); Logger.LogInfo($"Change Output: {changeOutput.Amount.ToString(false, true)} {changeOutput.ScriptPubKey.GetDestinationAddress(network)}"); Logger.LogInfo($"TxId: {res.Transaction.GetHash()}"); foundReceive = false; Assert.InRange(res.Transaction.Transaction.Outputs.Count, 1, 2); foreach (var output in res.Transaction.Transaction.Outputs) { if (output.ScriptPubKey == receive) { foundReceive = true; Assert.Equal(amountToSend, output.Value); } } Assert.True(foundReceive); #endregion MediumFee #region HighFee res = wallet.BuildTransaction(password, new PaymentIntent(receive, amountToSend, label: "foo"), FeeStrategy.TwentyMinutesConfirmationTargetStrategy, allowUnconfirmed: true); Assert.Equal(2, res.InnerWalletOutputs.Count()); Assert.Empty(res.OuterWalletOutputs); activeOutput = res.InnerWalletOutputs.Single(x => x.ScriptPubKey == receive); changeOutput = res.InnerWalletOutputs.Single(x => x.ScriptPubKey != receive); Assert.Equal(receive, activeOutput.ScriptPubKey); Assert.Equal(amountToSend, activeOutput.Amount); Assert.Contains(res.Transaction.Transaction.Outputs, x => x.Value == changeOutput.Amount); Logger.LogInfo($"{nameof(res.Fee)}: {res.Fee}"); Logger.LogInfo($"{nameof(res.FeePercentOfSent)}: {res.FeePercentOfSent} %"); Logger.LogInfo($"{nameof(res.SpendsUnconfirmed)}: {res.SpendsUnconfirmed}"); Logger.LogInfo($"Active Output: {activeOutput.Amount.ToString(false, true)} {activeOutput.ScriptPubKey.GetDestinationAddress(network)}"); Logger.LogInfo($"Change Output: {changeOutput.Amount.ToString(false, true)} {changeOutput.ScriptPubKey.GetDestinationAddress(network)}"); Logger.LogInfo($"TxId: {res.Transaction.GetHash()}"); foundReceive = false; Assert.InRange(res.Transaction.Transaction.Outputs.Count, 1, 2); foreach (var output in res.Transaction.Transaction.Outputs) { if (output.ScriptPubKey == receive) { foundReceive = true; Assert.Equal(amountToSend, output.Value); } } Assert.True(foundReceive); Assert.InRange(res.Fee, Money.Zero, res.Fee); Assert.InRange(res.Fee, res.Fee, res.Fee); await broadcaster.SendTransactionAsync(res.Transaction); #endregion HighFee #region MaxAmount receive = keyManager.GetNextReceiveKey("MaxAmount", out _).P2wpkhScript; res = wallet.BuildTransaction(password, new PaymentIntent(receive, MoneyRequest.CreateAllRemaining(), "foo"), FeeStrategy.SevenDaysConfirmationTargetStrategy, allowUnconfirmed: true); Assert.Single(res.InnerWalletOutputs); Assert.Empty(res.OuterWalletOutputs); activeOutput = res.InnerWalletOutputs.Single(); Assert.Equal(receive, activeOutput.ScriptPubKey); Assert.Single(res.Transaction.Transaction.Outputs); var maxBuiltTxOutput = res.Transaction.Transaction.Outputs.Single(); Assert.Equal(receive, maxBuiltTxOutput.ScriptPubKey); Assert.Equal(wallet.Coins.Where(x => !x.Unavailable).Sum(x => x.Amount) - res.Fee, maxBuiltTxOutput.Value); await broadcaster.SendTransactionAsync(res.Transaction); #endregion MaxAmount #region InputSelection receive = keyManager.GetNextReceiveKey("InputSelection", out _).P2wpkhScript; var inputCountBefore = res.SpentCoins.Count(); res = wallet.BuildTransaction(password, new PaymentIntent(receive, MoneyRequest.CreateAllRemaining(), "foo"), FeeStrategy.SevenDaysConfirmationTargetStrategy, allowUnconfirmed: true, allowedInputs: wallet.Coins.Where(x => !x.Unavailable).Select(x => x.OutPoint).Take(1)); Assert.Single(res.InnerWalletOutputs); Assert.Empty(res.OuterWalletOutputs); activeOutput = res.InnerWalletOutputs.Single(x => x.ScriptPubKey == receive); Assert.True(inputCountBefore >= res.SpentCoins.Count()); Assert.Equal(res.SpentCoins.Count(), res.Transaction.Transaction.Inputs.Count); Assert.Equal(receive, activeOutput.ScriptPubKey); Logger.LogInfo($"{nameof(res.Fee)}: {res.Fee}"); Logger.LogInfo($"{nameof(res.FeePercentOfSent)}: {res.FeePercentOfSent} %"); Logger.LogInfo($"{nameof(res.SpendsUnconfirmed)}: {res.SpendsUnconfirmed}"); Logger.LogInfo($"Active Output: {activeOutput.Amount.ToString(false, true)} {activeOutput.ScriptPubKey.GetDestinationAddress(network)}"); Logger.LogInfo($"TxId: {res.Transaction.GetHash()}"); Assert.Single(res.Transaction.Transaction.Outputs); res = wallet.BuildTransaction(password, new PaymentIntent(receive, MoneyRequest.CreateAllRemaining(), "foo"), FeeStrategy.SevenDaysConfirmationTargetStrategy, allowUnconfirmed: true, allowedInputs: new[] { res.SpentCoins.Select(x => x.OutPoint).First() }); Assert.Single(res.InnerWalletOutputs); Assert.Empty(res.OuterWalletOutputs); activeOutput = res.InnerWalletOutputs.Single(x => x.ScriptPubKey == receive); Assert.Single(res.Transaction.Transaction.Inputs); Assert.Single(res.Transaction.Transaction.Outputs); Assert.Single(res.SpentCoins); #endregion InputSelection #region Labeling Script receive2 = keyManager.GetNextReceiveKey("foo", out _).P2wpkhScript; res = wallet.BuildTransaction(password, new PaymentIntent(receive2, MoneyRequest.CreateAllRemaining(), "my label"), FeeStrategy.SevenDaysConfirmationTargetStrategy, allowUnconfirmed: true); Assert.Single(res.InnerWalletOutputs); Assert.Equal("foo, my label", res.InnerWalletOutputs.Single().Label); amountToSend = wallet.Coins.Where(x => !x.Unavailable).Sum(x => x.Amount) / 3; res = wallet.BuildTransaction( password, new PaymentIntent( new DestinationRequest(new Key(), amountToSend, label: "outgoing"), new DestinationRequest(new Key(), amountToSend, label: "outgoing2")), FeeStrategy.SevenDaysConfirmationTargetStrategy, allowUnconfirmed: true); Assert.Single(res.InnerWalletOutputs); Assert.Equal(2, res.OuterWalletOutputs.Count()); IEnumerable <string> change = res.InnerWalletOutputs.Single().Label.Labels; Assert.Contains("outgoing", change); Assert.Contains("outgoing2", change); await broadcaster.SendTransactionAsync(res.Transaction); IEnumerable <SmartCoin> unconfirmedCoins = wallet.Coins.Where(x => x.Height == Height.Mempool).ToArray(); IEnumerable <string> unconfirmedCoinLabels = unconfirmedCoins.SelectMany(x => x.Label.Labels).ToArray(); Assert.Contains("outgoing", unconfirmedCoinLabels); Assert.Contains("outgoing2", unconfirmedCoinLabels); IEnumerable <string> allKeyLabels = keyManager.GetKeys().SelectMany(x => x.Label.Labels); Assert.Contains("outgoing", allKeyLabels); Assert.Contains("outgoing2", allKeyLabels); Interlocked.Exchange(ref Common.FiltersProcessedByWalletCount, 0); await rpc.GenerateAsync(1); await Common.WaitForFiltersToBeProcessedAsync(TimeSpan.FromSeconds(120), 1); var bestHeight = new Height(bitcoinStore.SmartHeaderChain.TipHeight); IEnumerable <string> confirmedCoinLabels = wallet.Coins.Where(x => x.Height == bestHeight).SelectMany(x => x.Label.Labels); Assert.Contains("outgoing", confirmedCoinLabels); Assert.Contains("outgoing2", confirmedCoinLabels); allKeyLabels = keyManager.GetKeys().SelectMany(x => x.Label.Labels); Assert.Contains("outgoing", allKeyLabels); Assert.Contains("outgoing2", allKeyLabels); #endregion Labeling #region AllowedInputsDisallowUnconfirmed inputCountBefore = res.SpentCoins.Count(); receive = keyManager.GetNextReceiveKey("AllowedInputsDisallowUnconfirmed", out _).P2wpkhScript; var allowedInputs = wallet.Coins.Where(x => !x.Unavailable).Select(x => x.OutPoint).Take(1); var toSend = new PaymentIntent(receive, MoneyRequest.CreateAllRemaining(), "fizz"); // covers: // disallow unconfirmed with allowed inputs res = wallet.BuildTransaction(password, toSend, FeeStrategy.TwentyMinutesConfirmationTargetStrategy, false, allowedInputs: allowedInputs); activeOutput = res.InnerWalletOutputs.Single(x => x.ScriptPubKey == receive); Assert.Single(res.InnerWalletOutputs); Assert.Empty(res.OuterWalletOutputs); Assert.Equal(receive, activeOutput.ScriptPubKey); Logger.LogDebug($"{nameof(res.Fee)}: {res.Fee}"); Logger.LogDebug($"{nameof(res.FeePercentOfSent)}: {res.FeePercentOfSent} %"); Logger.LogDebug($"{nameof(res.SpendsUnconfirmed)}: {res.SpendsUnconfirmed}"); Logger.LogDebug($"Active Output: {activeOutput.Amount.ToString(false, true)} {activeOutput.ScriptPubKey.GetDestinationAddress(network)}"); Logger.LogDebug($"TxId: {res.Transaction.GetHash()}"); Assert.True(inputCountBefore >= res.SpentCoins.Count()); Assert.False(res.SpendsUnconfirmed); Assert.Single(res.Transaction.Transaction.Inputs); Assert.Single(res.Transaction.Transaction.Outputs); Assert.Single(res.SpentCoins); Assert.True(inputCountBefore >= res.SpentCoins.Count()); Assert.Equal(res.SpentCoins.Count(), res.Transaction.Transaction.Inputs.Count); #endregion AllowedInputsDisallowUnconfirmed #region CustomChange // covers: // customchange // feePc > 1 var k1 = new Key(); var k2 = new Key(); res = wallet.BuildTransaction( password, new PaymentIntent( new DestinationRequest(k1, MoneyRequest.CreateChange()), new DestinationRequest(k2, Money.Coins(0.0003m), label: "outgoing")), FeeStrategy.TwentyMinutesConfirmationTargetStrategy); Assert.Contains(k1.ScriptPubKey, res.OuterWalletOutputs.Select(x => x.ScriptPubKey)); Assert.Contains(k2.ScriptPubKey, res.OuterWalletOutputs.Select(x => x.ScriptPubKey)); #endregion CustomChange #region FeePcHigh res = wallet.BuildTransaction( password, new PaymentIntent(new Key(), Money.Coins(0.0003m), label: "outgoing"), FeeStrategy.TwentyMinutesConfirmationTargetStrategy); Assert.True(res.FeePercentOfSent > 1); var newChangeK = keyManager.GenerateNewKey("foo", KeyState.Clean, isInternal: true); res = wallet.BuildTransaction( password, new PaymentIntent( new DestinationRequest(newChangeK.P2wpkhScript, MoneyRequest.CreateChange(), "boo"), new DestinationRequest(new Key(), Money.Coins(0.0003m), label: "outgoing")), FeeStrategy.TwentyMinutesConfirmationTargetStrategy); Assert.True(res.FeePercentOfSent > 1); Assert.Single(res.OuterWalletOutputs); Assert.Single(res.InnerWalletOutputs); SmartCoin changeRes = res.InnerWalletOutputs.Single(); Assert.Equal(newChangeK.P2wpkhScript, changeRes.ScriptPubKey); Assert.Equal(newChangeK.Label, changeRes.Label); Assert.Equal(KeyState.Clean, newChangeK.KeyState); // Still clean, because the tx wasn't yet propagated. #endregion FeePcHigh } finally { bitcoinStore.IndexStore.NewFilter -= Common.Wallet_NewFilterProcessed; await walletManager.RemoveAndStopAllAsync(CancellationToken.None); // Dispose wasabi synchronizer service. if (synchronizer is { })
public async Task 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(); } }
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(); } }
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 { })
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(); } }
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(); } }