diff --git a/.gitattributes b/.gitattributes index 1ff0c423042..7141dac28a8 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,6 +2,7 @@ # Set default behavior to automatically normalize line endings. ############################################################################### * text=auto +**/osx64/Tor/tor text eol=lf ############################################################################### # Set default behavior for command prompt diff. diff --git a/WalletWasabi.Documentation/Guides/DeterministicBuildGuide.md b/WalletWasabi.Documentation/Guides/DeterministicBuildGuide.md index 936285344e3..90f9ee586da 100644 --- a/WalletWasabi.Documentation/Guides/DeterministicBuildGuide.md +++ b/WalletWasabi.Documentation/Guides/DeterministicBuildGuide.md @@ -1,68 +1,91 @@ -> Reproducible [or deterministic] builds are a set of software development practices that create an independently-verifiable path from source to binary code.- https://reproducible-builds.org/ +# Guide for deterministic builds -This guide describes how to reproduce Wasabi's builds. If you got stuck with these instructions, take a look at how to build Wasabi from source code: https://github.com/zkSNACKs/WalletWasabi#build-from-source-code +The term *deterministic builds* is [defined](https://reproducible-builds.org/) as follows: -# 1. Assert Correct Environment +> Reproducible [or deterministic] builds are a set of software development practices that create an independently-verifiable path from source to binary code. -In order to reproduce Wasabi's builds you need Git, Windows 10 and the version of .NET Core SDK that was the most recent in the time of building the release. +This guide describes how to reproduce Wasabi's builds. If you get stuck with these instructions, take a look at [how to build Wasabi from source code](https://docs.wasabiwallet.io/using-wasabi/BuildSource.html). -# 2. Reproduce Builds +**Warning:** Reproducible builds were introduced in [1.1.3 release](https://github.com/zkSNACKs/WalletWasabi/releases/tag/v1.1.3), you cannot use these instructions for older versions! + +## 1. Assert correct environment + +In order to reproduce Wasabi's builds, you need [git](https://git-scm.com/) package, Windows 10, and the version of [.NET Core SDK](https://dotnet.microsoft.com/download/dotnet-core) that was used by the Wasabi team to produce the release. The latest version of .NET Core SDK is always used, unless specified otherwise in the release notes of Wasabi Wallet. + +## 2. Reproduce builds + +You can see the list of Wasabi releases here: https://github.com/zkSNACKs/WalletWasabi/releases. Please note that each release has a git tag assigned, which is useful in the following instructions: ```sh -git clone https://github.com/zkSNACKs/WalletWasabi.git -git checkout {hash of the release} # This works from 1.1.3 release, https://github.com/zkSNACKs/WalletWasabi/releases -cd WalletWasabi/WalletWasabi.Packager/ -dotnet clean -dotnet restore --locked-mode +# The following command downloads only a single git branch. However, you can clone the whole repository, which is bigger. +git clone --depth 1 --branch https://github.com/zkSNACKs/WalletWasabi.git # where `` may be, for example, `v1.1.11.1`. +cd WalletWasabi/WalletWasabi.Packager +dotnet nuget locals all --clear +dotnet restore dotnet build dotnet run -- --onlybinaries ``` -This will build our binaries for Windows, OSX and Linux from source code and open them in a file explorer for you. +The previous commands produce Wasabi's binaries for Windows, macOS and Linux. Also, for your convenience, a new file explorer window will navigate you to the binaries location - i.e. `WalletWasabi\\WalletWasabi.Gui\\bin\\dist`. ![](https://i.imgur.com/8XAQzz4.png) -# 3. Verify Builds +## 3. Verify builds -You can compare our binaries with the downloads we have on the website: https://wasabiwallet.io/ -In order to end-to-end verify all the downloaded packages you need a Windows, a Linux, and an OSX machine. +Now, we will attempt to verify the binaries you have just compiled with the officially distributed binaries on https://wasabiwallet.io website. Please download those packages from the website, you should see the following files in your File Explorer: ![](https://i.imgur.com/aI9Kx0c.png) -## Windows +### Windows -After you installed Wasabi from the `.msi`, it will be in `C:\Program Files\WasabiWallet` folder. You can compare it with your build: - -```sh -git diff --no-index win7-x64 "C:\Program Files\WasabiWallet" -``` +* Install Wasabi using `Wasabi-.msi` file. It will install to `C:\Program Files\WasabiWallet` directory. +* Start `cmd` or Powershell and navigate to the `dist` directory. +* Execute the following command: + ```sh + git diff --no-index "win7-x64" "C:\Program Files\WasabiWallet" + ``` +* Make sure that there is **NO** difference reported by the command. -## Linux && OSX +### Linux & macOS -You can use the Windows Subsystem for Linux to verify all the packages in one go. At the time of writing this guide we provide a `.tar.gz` and a `.deb` package for Linux and .dmg for OSX. +You can use the [Windows Subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/) to verify all the packages in one go. At the time of writing this guide we provide `.tar.gz` and `.deb` packages for Linux and `.dmg` package for macOS. Install the `.deb` package and extract the `tar.gz` and `.dmg` packages, then compare them with your build. -After installing WSL, just type `wsl` in explorer where your downloaded and built packages are located. +After [installing WSL](https://docs.microsoft.com/en-us/windows/wsl/install-win10), just type `wsl` in File Explorer where your downloaded and built packages are located. ![](https://i.imgur.com/yRUjxvG.png) -### .deb +#### .deb ```sh sudo dpkg -i Wasabi-1.1.6.deb git diff --no-index linux-x64/ /usr/local/bin/wasabiwallet/ ``` -### .tar.gz +#### .tar.gz ```sh tar -pxzf Wasabi-1.1.6.tar.gz git diff --no-index linux-x64/ Wasabi-1.1.6 ``` -### .dmg +*There could be warnings regarding SOS_README.md that it differs in line endings. That is a text file and it has no effect on the running software.* + +#### .dmg + +According to Apple documentation, the signature that is used to ensure the integrity of the software is added into the binary itself - so it will manipulate the content of the files. + +> If the code is universal, the object code for each slice (architecture) is signed separately. This signature is stored within the binary file itself. + +[Source](https://developer.apple.com/library/archive/documentation/Security/Conceptual/CodeSigningGuide/AboutCS/AboutCS.html#//apple_ref/doc/uid/TP40005929-CH3-SW3) + +According to this, it is impossible to have both deterministic build and code signature on macOS. macOS Gatekeeper won't let you run software without it. Thus, Wasabi only applies code signature, but no deterministic build for macOS. + +There is an issue [here](https://github.com/zkSNACKs/WalletWasabi/issues/4110) for further discussion. + +With the following method you can check the differences by yourself: -You will need to install `7z` (or something else) to extract the `.dmg`: `sudo apt install p7zip-full` +You will need to install `7z` (or something else) to extract the `.dmg`. You can do that using `sudo apt install p7zip-full` command. ```sh 7z x Wasabi-1.1.6.dmg -oWasabiOsx diff --git a/WalletWasabi.Gui/Controls/StatusBar.xaml b/WalletWasabi.Gui/Controls/StatusBar.xaml index da365e62b75..64faac09146 100644 --- a/WalletWasabi.Gui/Controls/StatusBar.xaml +++ b/WalletWasabi.Gui/Controls/StatusBar.xaml @@ -1,4 +1,4 @@ - - - - + + + diff --git a/WalletWasabi.Gui/Controls/WalletExplorer/CoinInfoTabView.xaml b/WalletWasabi.Gui/Controls/WalletExplorer/CoinInfoTabView.xaml index 7355600e60b..f45db238584 100644 --- a/WalletWasabi.Gui/Controls/WalletExplorer/CoinInfoTabView.xaml +++ b/WalletWasabi.Gui/Controls/WalletExplorer/CoinInfoTabView.xaml @@ -1,4 +1,4 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/WalletWasabi.Gui/Controls/WalletExplorer/CoinListView.xaml b/WalletWasabi.Gui/Controls/WalletExplorer/CoinListView.xaml index bb587df9d27..5406659a49e 100644 --- a/WalletWasabi.Gui/Controls/WalletExplorer/CoinListView.xaml +++ b/WalletWasabi.Gui/Controls/WalletExplorer/CoinListView.xaml @@ -1,10 +1,9 @@ - + @@ -48,7 +47,7 @@ - + @@ -79,9 +78,7 @@ - - - + @@ -89,12 +86,12 @@ - + - + @@ -109,20 +106,32 @@ - - - + + + + + + + + + + - - - - + + + - + + + + + + + diff --git a/WalletWasabi.Gui/Controls/WalletExplorer/CoinListViewModel.cs b/WalletWasabi.Gui/Controls/WalletExplorer/CoinListViewModel.cs index b1f62d1d106..6a1f6936f50 100644 --- a/WalletWasabi.Gui/Controls/WalletExplorer/CoinListViewModel.cs +++ b/WalletWasabi.Gui/Controls/WalletExplorer/CoinListViewModel.cs @@ -42,7 +42,7 @@ public class CoinListViewModel : ViewModelBase private bool? _selectPrivateCheckBoxState; private bool? _selectNonPrivateCheckBoxState; private GridLength _coinJoinStatusWidth; - private SortOrder _clustersSortDirection; + private SortOrder _clusterSortDirection; private Money _selectedAmount; private bool _isAnyCoinSelected; private bool _labelExposeCommonOwnershipWarning; @@ -95,10 +95,10 @@ public CoinListViewModel(Wallet wallet, bool canDequeueCoins = false, bool displ .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(x => SortColumn(x, nameof(AmountSortDirection))); - this.WhenAnyValue(x => x.ClustersSortDirection) + this.WhenAnyValue(x => x.ClusterSortDirection) .Where(x => x != SortOrder.None) .ObserveOn(RxApp.MainThreadScheduler) - .Subscribe(x => SortColumn(x, nameof(ClustersSortDirection))); + .Subscribe(x => SortColumn(x, nameof(ClusterSortDirection))); this.WhenAnyValue(x => x.StatusSortDirection) .Where(x => x != SortOrder.None) @@ -245,10 +245,10 @@ public SortOrder PrivacySortDirection set => this.RaiseAndSetIfChanged(ref _privacySortDirection, value); } - public SortOrder ClustersSortDirection + public SortOrder ClusterSortDirection { - get => _clustersSortDirection; - set => this.RaiseAndSetIfChanged(ref _clustersSortDirection, value); + get => _clusterSortDirection; + set => this.RaiseAndSetIfChanged(ref _clusterSortDirection, value); } public Money SelectedAmount @@ -338,7 +338,7 @@ private void ValidateSavedColumnConfig() if (savedCol != nameof(AmountSortDirection) & savedCol != nameof(PrivacySortDirection) - & savedCol != nameof(ClustersSortDirection) + & savedCol != nameof(ClusterSortDirection) & savedCol != nameof(StatusSortDirection)) { SelectedColumnPreference = new SortingPreference(SortOrder.Increasing, nameof(AmountSortDirection)); @@ -356,7 +356,7 @@ private void SortColumn(SortOrder sortOrder, string target, bool saveToUiConfig AmountSortDirection = sortPref.Match(sortOrder, nameof(AmountSortDirection)); PrivacySortDirection = sortPref.Match(sortOrder, nameof(PrivacySortDirection)); - ClustersSortDirection = sortPref.Match(sortOrder, nameof(ClustersSortDirection)); + ClusterSortDirection = sortPref.Match(sortOrder, nameof(ClusterSortDirection)); StatusSortDirection = sortPref.Match(sortOrder, nameof(StatusSortDirection)); } @@ -375,11 +375,11 @@ private void RefreshOrdering() ? sortExpression.ThenByAscending(cvm => cvm.AnonymitySet) : sortExpression.ThenByDescending(cvm => cvm.AnonymitySet); } - else if (ClustersSortDirection != SortOrder.None) + else if (ClusterSortDirection != SortOrder.None) { - MyComparer = ClustersSortDirection == SortOrder.Increasing - ? sortExpression.ThenByAscending(cvm => cvm.Clusters) - : sortExpression.ThenByDescending(cvm => cvm.Clusters); + MyComparer = ClusterSortDirection == SortOrder.Increasing + ? sortExpression.ThenByAscending(cvm => cvm.Cluster) + : sortExpression.ThenByDescending(cvm => cvm.Cluster); } else if (StatusSortDirection != SortOrder.None) { diff --git a/WalletWasabi.Gui/Controls/WalletExplorer/CoinViewModel.cs b/WalletWasabi.Gui/Controls/WalletExplorer/CoinViewModel.cs index 72cf5a750c7..1db380d6ee8 100644 --- a/WalletWasabi.Gui/Controls/WalletExplorer/CoinViewModel.cs +++ b/WalletWasabi.Gui/Controls/WalletExplorer/CoinViewModel.cs @@ -62,9 +62,9 @@ public CoinViewModel(Wallet wallet, CoinListViewModel owner, SmartCoin model) .DisposeWith(Disposables); _cluster = Model - .WhenAnyValue(x => x.Clusters, x => x.Clusters.Labels) + .WhenAnyValue(x => x.Cluster, x => x.Cluster.Labels) .Select(x => x.Item2.ToString()) - .ToProperty(this, x => x.Clusters, scheduler: RxApp.MainThreadScheduler) + .ToProperty(this, x => x.Cluster, scheduler: RxApp.MainThreadScheduler) .DisposeWith(Disposables); _unavailable = Model @@ -97,7 +97,7 @@ public CoinViewModel(Wallet wallet, CoinListViewModel owner, SmartCoin model) .Subscribe(_ => { this.RaisePropertyChanged(nameof(AmountBtc)); - this.RaisePropertyChanged(nameof(Clusters)); + this.RaisePropertyChanged(nameof(Cluster)); }).DisposeWith(Disposables); DequeueCoin = ReactiveCommand.Create(() => Owner.PressDequeue(Model), this.WhenAnyValue(x => x.CoinJoinInProgress)); @@ -117,12 +117,12 @@ public CoinViewModel(Wallet wallet, CoinListViewModel owner, SmartCoin model) shell.Select(coinInfo); }); - CopyClusters = ReactiveCommand.CreateFromTask(async () => await Application.Current.Clipboard.SetTextAsync(Clusters)); + CopyCluster = ReactiveCommand.CreateFromTask(async () => await Application.Current.Clipboard.SetTextAsync(Cluster)); Observable .Merge(DequeueCoin.ThrownExceptions) // Don't notify about it. Dequeue failure (and success) is notified by other mechanism. .Merge(OpenCoinInfo.ThrownExceptions) - .Merge(CopyClusters.ThrownExceptions) + .Merge(CopyCluster.ThrownExceptions) .ObserveOn(RxApp.TaskpoolScheduler) .Subscribe(ex => Logger.LogError(ex)); } @@ -135,7 +135,7 @@ public CoinViewModel(Wallet wallet, CoinListViewModel owner, SmartCoin model) public bool CanBeDequeued => Owner.CanDequeueCoins; public ReactiveCommand DequeueCoin { get; } public ReactiveCommand OpenCoinInfo { get; } - public ReactiveCommand CopyClusters { get; } + public ReactiveCommand CopyCluster { get; } public SmartCoin Model { get; } @@ -188,7 +188,7 @@ public bool IsSelected public string InCoinJoin => Model.CoinJoinInProgress ? "Yes" : "No"; - public string Clusters => _cluster?.Value ?? ""; + public string Cluster => _cluster?.Value ?? ""; public string PubKey => Model.HdPubKey?.PubKey?.ToString() ?? ""; diff --git a/WalletWasabi.Gui/Controls/WalletExplorer/HistoryTabView.xaml b/WalletWasabi.Gui/Controls/WalletExplorer/HistoryTabView.xaml index 6609edb8b40..d2ff18242a4 100644 --- a/WalletWasabi.Gui/Controls/WalletExplorer/HistoryTabView.xaml +++ b/WalletWasabi.Gui/Controls/WalletExplorer/HistoryTabView.xaml @@ -20,16 +20,17 @@ - + + - + @@ -58,6 +59,7 @@ + diff --git a/WalletWasabi.Gui/Controls/WalletExplorer/HistoryTabViewModel.cs b/WalletWasabi.Gui/Controls/WalletExplorer/HistoryTabViewModel.cs index d290cfc0efc..e39785692d8 100644 --- a/WalletWasabi.Gui/Controls/WalletExplorer/HistoryTabViewModel.cs +++ b/WalletWasabi.Gui/Controls/WalletExplorer/HistoryTabViewModel.cs @@ -25,6 +25,7 @@ public class HistoryTabViewModel : WasabiDocumentTabViewModel, IWalletViewModel private SortOrder _dateSortDirection; private SortOrder _amountSortDirection; private SortOrder _transactionSortDirection; + private SortOrder _labelSortDirection; public HistoryTabViewModel(Wallet wallet) : base("History") @@ -57,6 +58,11 @@ public HistoryTabViewModel(Wallet wallet) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(x => SortColumn(x, nameof(TransactionSortDirection))); + this.WhenAnyValue(x => x.LabelSortDirection) + .Where(x => x != SortOrder.None) + .ObserveOn(RxApp.MainThreadScheduler) + .Subscribe(x => SortColumn(x, nameof(LabelSortDirection))); + SortCommand.ThrownExceptions .ObserveOn(RxApp.TaskpoolScheduler) .Subscribe(ex => @@ -104,6 +110,12 @@ public SortOrder TransactionSortDirection set => this.RaiseAndSetIfChanged(ref _transactionSortDirection, value); } + public SortOrder LabelSortDirection + { + get => _labelSortDirection; + set => this.RaiseAndSetIfChanged(ref _labelSortDirection, value); + } + public override void OnOpen(CompositeDisposable disposables) { base.OnOpen(disposables); @@ -170,9 +182,10 @@ private void ValidateSavedColumnConfig() if (savedCol != nameof(DateSortDirection) & savedCol != nameof(AmountSortDirection) - & savedCol != nameof(TransactionSortDirection)) + & savedCol != nameof(TransactionSortDirection) + & savedCol != nameof(LabelSortDirection)) { - Global.UiConfig.HistoryTabViewSortingPreference = new SortingPreference(SortOrder.Increasing, nameof(DateSortDirection)); + Global.UiConfig.HistoryTabViewSortingPreference = new SortingPreference(SortOrder.Decreasing, nameof(DateSortDirection)); } } @@ -188,11 +201,25 @@ private void SortColumn(SortOrder sortOrder, string target, bool saveToUiConfig DateSortDirection = sortPref.Match(sortOrder, nameof(DateSortDirection)); AmountSortDirection = sortPref.Match(sortOrder, nameof(AmountSortDirection)); TransactionSortDirection = sortPref.Match(sortOrder, nameof(TransactionSortDirection)); + LabelSortDirection = sortPref.Match(sortOrder, nameof(LabelSortDirection)); } private void RefreshOrdering() { - if (TransactionSortDirection != SortOrder.None) + if (LabelSortDirection != SortOrder.None) + { + switch (LabelSortDirection) + { + case SortOrder.Increasing: + Transactions = new ObservableCollection(_transactions.OrderBy(t => t.Label)); + break; + + case SortOrder.Decreasing: + Transactions = new ObservableCollection(_transactions.OrderByDescending(t => t.Label)); + break; + } + } + else if (TransactionSortDirection != SortOrder.None) { switch (TransactionSortDirection) { diff --git a/WalletWasabi.Gui/Controls/WalletExplorer/ReceiveTabView.xaml b/WalletWasabi.Gui/Controls/WalletExplorer/ReceiveTabView.xaml index 9807aa39036..ec3ecf620ce 100644 --- a/WalletWasabi.Gui/Controls/WalletExplorer/ReceiveTabView.xaml +++ b/WalletWasabi.Gui/Controls/WalletExplorer/ReceiveTabView.xaml @@ -1,4 +1,4 @@ - - + @@ -60,12 +60,12 @@ - + - + diff --git a/WalletWasabi.Gui/Controls/WalletExplorer/ReceiveTabViewModel.cs b/WalletWasabi.Gui/Controls/WalletExplorer/ReceiveTabViewModel.cs index e906b6305c2..7f7e0a0bec4 100644 --- a/WalletWasabi.Gui/Controls/WalletExplorer/ReceiveTabViewModel.cs +++ b/WalletWasabi.Gui/Controls/WalletExplorer/ReceiveTabViewModel.cs @@ -42,7 +42,7 @@ public ReceiveTabViewModel(Wallet wallet) LabelSuggestion.Label = label; if (label.IsEmpty) { - NotificationHelpers.Warning("Known By is required."); + NotificationHelpers.Warning("Label is required."); return; } diff --git a/WalletWasabi.Gui/Controls/WalletExplorer/SendControlView.xaml b/WalletWasabi.Gui/Controls/WalletExplorer/SendControlView.xaml index 01d17a25320..f5cd6e832f8 100644 --- a/WalletWasabi.Gui/Controls/WalletExplorer/SendControlView.xaml +++ b/WalletWasabi.Gui/Controls/WalletExplorer/SendControlView.xaml @@ -1,4 +1,4 @@ - - - - - + + diff --git a/WalletWasabi.Gui/Controls/WalletExplorer/SendControlViewModel.cs b/WalletWasabi.Gui/Controls/WalletExplorer/SendControlViewModel.cs index caae5f56c53..2cfb7075bf8 100644 --- a/WalletWasabi.Gui/Controls/WalletExplorer/SendControlViewModel.cs +++ b/WalletWasabi.Gui/Controls/WalletExplorer/SendControlViewModel.cs @@ -220,7 +220,7 @@ protected SendControlViewModel(Wallet wallet, string title) LabelSuggestion.Label = label; if (!IsMax && label.IsEmpty) { - NotificationHelpers.Warning("Known By is required.", ""); + NotificationHelpers.Warning("Label is required.", ""); return; } diff --git a/WalletWasabi.Gui/Controls/WalletExplorer/SendTabViewModel.cs b/WalletWasabi.Gui/Controls/WalletExplorer/SendTabViewModel.cs index a6078ad68a9..619477e934c 100644 --- a/WalletWasabi.Gui/Controls/WalletExplorer/SendTabViewModel.cs +++ b/WalletWasabi.Gui/Controls/WalletExplorer/SendTabViewModel.cs @@ -3,7 +3,6 @@ using ReactiveUI; using System; using System.Collections.Generic; -using System.Net.Http; using System.Threading; using System.Threading.Tasks; using WalletWasabi.Blockchain.TransactionBuilding; @@ -52,24 +51,7 @@ protected override async Task BuildTransaction(string password, PaymentIntent pa PSBT signedPsbt = null; try { - try - { - signedPsbt = await client.SignTxAsync(Wallet.KeyManager.MasterFingerprint.Value, result.Psbt, cts.Token); - } - catch (PSBTException ex) when (ex.Message.Contains("NullFail")) - { - NotificationHelpers.Warning("Fall back to Unverified Inputs Mode, trying to sign again."); - - // Ledger Nano S hackfix https://github.com/MetacoSA/NBitcoin/pull/888 - - var noinputtx = result.Psbt.Clone(); - foreach (var input in noinputtx.Inputs) - { - input.NonWitnessUtxo = null; - } - - signedPsbt = await client.SignTxAsync(Wallet.KeyManager.MasterFingerprint.Value, noinputtx, cts.Token); - } + signedPsbt = await client.SignTxAsync(Wallet.KeyManager.MasterFingerprint.Value, result.Psbt, cts.Token); } catch (HwiException) { @@ -107,18 +89,18 @@ private IPayjoinClient GetPayjoinClient() { if (payjoinEndPointUri.DnsSafeHost.EndsWith(".onion", StringComparison.OrdinalIgnoreCase)) { - Logger.LogWarning("Payjoin server is a hidden service but Tor is disabled. Ignoring..."); + Logger.LogWarning("Payjoin server is an onion service but Tor is disabled. Ignoring..."); return null; } if (Global.Config.Network == Network.Main && payjoinEndPointUri.Scheme != Uri.UriSchemeHttps) { - Logger.LogWarning("Payjoin server is not exposed as onion hidden service nor https. Ignoring..."); + Logger.LogWarning("Payjoin server is not exposed as an onion service nor https. Ignoring..."); return null; } } - return new PayjoinClient(payjoinEndPointUri, Global.TorManager.TorSocks5EndPoint); + return new PayjoinClient(payjoinEndPointUri, Global.Config.TorSocks5EndPoint); } return null; diff --git a/WalletWasabi.Gui/Controls/WalletExplorer/TransactionViewModel.cs b/WalletWasabi.Gui/Controls/WalletExplorer/TransactionViewModel.cs index 485b6e7a3b8..4353cc1058d 100644 --- a/WalletWasabi.Gui/Controls/WalletExplorer/TransactionViewModel.cs +++ b/WalletWasabi.Gui/Controls/WalletExplorer/TransactionViewModel.cs @@ -97,6 +97,7 @@ public void Refresh() this.RaisePropertyChanged(nameof(AmountBtc)); this.RaisePropertyChanged(nameof(TransactionId)); this.RaisePropertyChanged(nameof(DateTime)); + this.RaisePropertyChanged(nameof(Label)); } public async Task TryCopyTxIdToClipboardAsync() diff --git a/WalletWasabi.Gui/Controls/WalletExplorer/WalletExplorerView.xaml b/WalletWasabi.Gui/Controls/WalletExplorer/WalletExplorerView.xaml index e9a6d0bcdce..5074351938a 100644 --- a/WalletWasabi.Gui/Controls/WalletExplorer/WalletExplorerView.xaml +++ b/WalletWasabi.Gui/Controls/WalletExplorer/WalletExplorerView.xaml @@ -33,7 +33,7 @@ - - - - + diff --git a/WalletWasabi.Gui/Global.cs b/WalletWasabi.Gui/Global.cs index 329eb4c30f7..c1f1a6032f0 100644 --- a/WalletWasabi.Gui/Global.cs +++ b/WalletWasabi.Gui/Global.cs @@ -102,12 +102,13 @@ public Global(string dataDir, string torLogsFile, Config config, UiConfig uiConf WalletManager.OnDequeue += WalletManager_OnDequeue; WalletManager.WalletRelevantTransactionProcessed += WalletManager_WalletRelevantTransactionProcessed; - var indexStore = new IndexStore(Network, new SmartHeaderChain()); + var networkWorkFolderPath = Path.Combine(DataDir, "BitcoinStore", Network.ToString()); + var transactionStore = new AllTransactionStore(networkWorkFolderPath, Network); + var indexStore = new IndexStore(Path.Combine(networkWorkFolderPath, "IndexStore"), Network, new SmartHeaderChain()); + var mempoolService = new MempoolService(); + var blocks = new FileSystemBlockRepository(Path.Combine(networkWorkFolderPath, "Blocks"), Network); - BitcoinStore = new BitcoinStore( - Path.Combine(DataDir, "BitcoinStore"), Network, - indexStore, new AllTransactionStore(), new MempoolService() - ); + BitcoinStore = new BitcoinStore(indexStore, transactionStore, mempoolService, blocks); SingleInstanceChecker = new SingleInstanceChecker(Network); } @@ -144,7 +145,6 @@ public async Task InitializeNoWalletAsync() 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 }; @@ -196,10 +196,19 @@ public async Task InitializeNoWalletAsync() #region BitcoinStoreInitialization - await bstoreInitTask.ConfigureAwait(false); + try + { + 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); + // 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); + } + catch (Exception ex) when (!(ex is OperationCanceledException)) + { + // If our internal data structures in the Bitcoin Store gets corrupted, then it's better to rescan all the wallets. + WalletManager.SetMaxBestHeight(SmartHeader.GetStartingHeader(Network).Height); + throw; + } #endregion BitcoinStoreInitialization @@ -297,18 +306,7 @@ public async Task InitializeNoWalletAsync() } 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 do not connect to clearnet IPs at all. - // This of course makes the first setting unnecessary, 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; + Nodes = CreateAndConfigureNodesGroup(connectionParameters); RegTestMempoolServingNode = null; } @@ -371,7 +369,7 @@ public async Task InitializeNoWalletAsync() new SmartBlockProvider( new P2pBlockProvider(Nodes, BitcoinCoreNode, Synchronizer, Config.ServiceConfiguration, Network), Cache), - new FileSystemBlockRepository(blocksFolderPath, Network)); + BitcoinStore.BlockRepository); #endregion Blocks provider @@ -383,6 +381,22 @@ public async Task InitializeNoWalletAsync() } } + private NodesGroup CreateAndConfigureNodesGroup(NodeConnectionParameters connectionParameters) + { + var maximumNodeConnection = 12; + var bestEffortEndpointConnector = new BestEffortEndpointConnector(maximumNodeConnection / 2); + connectionParameters.EndpointConnector = bestEffortEndpointConnector; + if (Config.UseTor) + { + connectionParameters.TemplateBehaviors.Add(new SocksSettingsBehavior(Config.TorSocks5EndPoint, onlyForOnionHosts: false, networkCredential: null, streamIsolation: true)); + } + var nodes = new NodesGroup(Network, connectionParameters, requirements: Constants.NodeRequirements); + nodes.ConnectedNodes.Added += ConnectedNodes_OnAddedOrRemoved; + nodes.ConnectedNodes.Removed += ConnectedNodes_OnAddedOrRemoved; + nodes.MaximumNodeConnection = maximumNodeConnection; + return nodes; + } + private async Task InitializeAddressManagerBehaviorAsync() { var needsToDiscoverPeers = true; @@ -447,25 +461,13 @@ private async Task InitializeAddressManagerBehaviorAsync return addressManagerBehavior; } - private async Task AddKnownBitcoinFullNodeAsHiddenServiceAsync(AddressManager addressManager) + private void ConnectedNodes_OnAddedOrRemoved(object? sender, NodeEventArgs e) { - if (Network == Network.RegTest) - { - return; - } - - // curl -s https://bitnodes.21.co/api/v1/snapshots/latest/ | egrep -o '[a-z0-9]{16}\.onion:?[0-9]*' | sort -ru - // Then filtered to include only /Satoshi:0.17.x - var fullBaseDirectory = EnvironmentHelpers.GetFullBaseDirectory(); - - var onions = await File.ReadAllLinesAsync(Path.Combine(fullBaseDirectory, "OnionSeeds", $"{Network}OnionSeeds.txt")).ConfigureAwait(false); - - onions.Shuffle(); - foreach (var onion in onions.Take(60)) + if (Nodes.NodeConnectionParameters.EndpointConnector is BestEffortEndpointConnector bestEffortEndPointConnector) { - if (EndPointParser.TryParse(onion, Network.DefaultPort, out var endpoint)) + if (sender is NodesCollection nodesCollection) { - await addressManager.AddAsync(endpoint).ConfigureAwait(false); + bestEffortEndPointConnector.UpdateConnectedNodesCounter(nodesCollection.Count); } } } @@ -734,9 +736,10 @@ public async Task DisposeAsync() } } - var nodes = Nodes; - if (nodes is { }) + if (Nodes is { } nodes) { + nodes.ConnectedNodes.Added -= ConnectedNodes_OnAddedOrRemoved; + nodes.ConnectedNodes.Removed -= ConnectedNodes_OnAddedOrRemoved; nodes.Disconnect(); while (nodes.ConnectedNodes.Any(x => x.IsConnected)) { diff --git a/WalletWasabi.Gui/Rpc/WasabiJsonRpcService.cs b/WalletWasabi.Gui/Rpc/WasabiJsonRpcService.cs index fdaea9aef25..2b749bbd78f 100644 --- a/WalletWasabi.Gui/Rpc/WasabiJsonRpcService.cs +++ b/WalletWasabi.Gui/Rpc/WasabiJsonRpcService.cs @@ -167,6 +167,19 @@ public async Task SendTransactionAsync(PaymentInfo[] payments, OutPoint[ }; } + [JsonRpcMethod("broadcast")] + public async Task SendRawTransactionAsync(string txHex) + { + txHex = Guard.Correct(txHex); + var smartTx = new SmartTransaction(Transaction.Parse(txHex, Global.Network), Height.Mempool); + + await Global.TransactionBroadcaster.SendTransactionAsync(smartTx).ConfigureAwait(false); + return new + { + txid = smartTx.Transaction.GetHash() + }; + } + [JsonRpcMethod("gethistory")] public object[] GetHistory() { diff --git a/WalletWasabi.Gui/Shell/Commands/HelpCommands.cs b/WalletWasabi.Gui/Shell/Commands/HelpCommands.cs index e97472290b8..3c30f7b61b0 100644 --- a/WalletWasabi.Gui/Shell/Commands/HelpCommands.cs +++ b/WalletWasabi.Gui/Shell/Commands/HelpCommands.cs @@ -34,7 +34,7 @@ public HelpCommands(CommandIconService commandIconService) { try { - await IoHelpers.OpenBrowserAsync("https://www.reddit.com/r/WasabiWallet/"); + await IoHelpers.OpenBrowserAsync("https://github.com/zkSNACKs/WalletWasabi/discussions/5185"); } catch (Exception ex) { diff --git a/WalletWasabi.Gui/Suggestions/SuggestLabelView.xaml b/WalletWasabi.Gui/Suggestions/SuggestLabelView.xaml index e6ae9e9d126..738a8fc874c 100644 --- a/WalletWasabi.Gui/Suggestions/SuggestLabelView.xaml +++ b/WalletWasabi.Gui/Suggestions/SuggestLabelView.xaml @@ -1,4 +1,4 @@ - - + diff --git a/WalletWasabi.Gui/Tabs/AboutView.xaml b/WalletWasabi.Gui/Tabs/AboutView.xaml index aae6a2ed119..1ef7806d248 100644 --- a/WalletWasabi.Gui/Tabs/AboutView.xaml +++ b/WalletWasabi.Gui/Tabs/AboutView.xaml @@ -10,55 +10,55 @@ - + - + - + - + - + - + + + diff --git a/WalletWasabi.Gui/Tabs/SettingsView.xaml b/WalletWasabi.Gui/Tabs/SettingsView.xaml index d00279591de..a044b75f9b0 100644 --- a/WalletWasabi.Gui/Tabs/SettingsView.xaml +++ b/WalletWasabi.Gui/Tabs/SettingsView.xaml @@ -15,7 +15,7 @@ Changes will be applied after restarting the application. - + @@ -99,10 +99,6 @@ Custom change address. - - - Lurking Wife Mode, hides sensitive content. - diff --git a/WalletWasabi.Gui/Tabs/SettingsViewModel.cs b/WalletWasabi.Gui/Tabs/SettingsViewModel.cs index 6f4d4628a53..b5647f64c5a 100644 --- a/WalletWasabi.Gui/Tabs/SettingsViewModel.cs +++ b/WalletWasabi.Gui/Tabs/SettingsViewModel.cs @@ -99,12 +99,6 @@ public SettingsViewModel() : base("Settings") OpenConfigFileCommand = ReactiveCommand.CreateFromTask(OpenConfigFileAsync); - LurkingWifeModeCommand = ReactiveCommand.Create(() => - { - Global.UiConfig.LurkingWifeMode = !LurkingWifeMode; - Global.UiConfig.ToFile(); - }); - SetClearPinCommand = ReactiveCommand.Create(() => { var pinBoxText = PinBoxText; @@ -156,7 +150,6 @@ public SettingsViewModel() : base("Settings") Observable .Merge(OpenConfigFileCommand.ThrownExceptions) - .Merge(LurkingWifeModeCommand.ThrownExceptions) .Merge(SetClearPinCommand.ThrownExceptions) .Merge(TextBoxLostFocusCommand.ThrownExceptions) .ObserveOn(RxApp.TaskpoolScheduler) @@ -171,7 +164,6 @@ public SettingsViewModel() : base("Settings") private object ConfigLock { get; } = new object(); public ReactiveCommand OpenConfigFileCommand { get; } - public ReactiveCommand LurkingWifeModeCommand { get; } public ReactiveCommand SetClearPinCommand { get; } public ReactiveCommand TextBoxLostFocusCommand { get; } @@ -274,8 +266,6 @@ public string DustThreshold set => this.RaiseAndSetIfChanged(ref _dustThreshold, value); } - public bool LurkingWifeMode => Global.UiConfig.LurkingWifeMode; - public string PinBoxText { get => _pinBoxText; @@ -286,11 +276,6 @@ public override void OnOpen(CompositeDisposable disposables) { try { - Global.UiConfig - .WhenAnyValue(x => x.LurkingWifeMode) - .Subscribe(_ => this.RaisePropertyChanged(nameof(LurkingWifeMode))) - .DisposeWith(disposables); - _isPinSet = Global.UiConfig .WhenAnyValue(x => x.LockScreenPinHash, x => !string.IsNullOrWhiteSpace(x)) .ToProperty(this, x => x.IsPinSet, scheduler: RxApp.MainThreadScheduler) diff --git a/WalletWasabi.Gui/Tabs/WalletManager/HardwareWallets/ConnectHardwareWalletView.xaml b/WalletWasabi.Gui/Tabs/WalletManager/HardwareWallets/ConnectHardwareWalletView.xaml index 2f4bf1dc432..f2a26d0f699 100644 --- a/WalletWasabi.Gui/Tabs/WalletManager/HardwareWallets/ConnectHardwareWalletView.xaml +++ b/WalletWasabi.Gui/Tabs/WalletManager/HardwareWallets/ConnectHardwareWalletView.xaml @@ -16,7 +16,7 @@ - + diff --git a/WalletWasabi.Gui/Tabs/WalletManager/RecoverWallets/RecoverWalletViewModel.cs b/WalletWasabi.Gui/Tabs/WalletManager/RecoverWallets/RecoverWalletViewModel.cs index ee24f8d5c09..cd3bd13210a 100644 --- a/WalletWasabi.Gui/Tabs/WalletManager/RecoverWallets/RecoverWalletViewModel.cs +++ b/WalletWasabi.Gui/Tabs/WalletManager/RecoverWallets/RecoverWalletViewModel.cs @@ -38,18 +38,21 @@ public RecoverWalletViewModel(WalletManagerViewModel owner) : base("Recover Wall this.ValidateProperty(x => x.Password, ValidatePassword); this.ValidateProperty(x => x.MinGapLimit, ValidateMinGapLimit); - this.ValidateProperty(x => x.AccountKeyPath, ValidateKeyPath); + this.ValidateProperty(x => x.AccountKeyPath, ValidateAccountKeyPath); MnemonicWords = ""; - RecoverCommand = ReactiveCommand.Create(() => - { - RecoverWallet(owner); - }, - Observable.FromEventPattern(this, nameof(ErrorsChanged)) + var canExecute = Observable + .Merge(Observable.FromEventPattern(this, nameof(ErrorsChanged)).Select(_ => Unit.Default)) + .Merge(this.WhenAnyValue(x => x.MnemonicWords).Select(_ => Unit.Default)) .ObserveOn(RxApp.MainThreadScheduler) - .Select(_ => !Validations.AnyErrors) - ); + .Select(_ => + { + var numberOfWords = MnemonicWords.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length; + return !Validations.AnyErrors && (numberOfWords == 12 || numberOfWords == 15 || numberOfWords == 18 || numberOfWords == 21 || numberOfWords == 24); + }); + + RecoverCommand = ReactiveCommand.Create(() => RecoverWallet(owner), canExecute); this.WhenAnyValue(x => x.MnemonicWords).Subscribe(UpdateSuggestions); @@ -220,13 +223,21 @@ private void ValidateMinGapLimit(IValidationErrors errors) } } - private void ValidateKeyPath(IValidationErrors errors) + private void ValidateAccountKeyPath(IValidationErrors errors) { if (string.IsNullOrWhiteSpace(AccountKeyPath)) { errors.Add(ErrorSeverity.Error, "Path is not valid."); } - else if (!KeyPath.TryParse(AccountKeyPath, out _)) + else if (KeyPath.TryParse(AccountKeyPath, out var keyPath)) + { + var accountKeyPath = keyPath.GetAccountKeyPath(); + if (keyPath.Length != accountKeyPath.Length || accountKeyPath.Length != KeyManager.DefaultAccountKeyPath.Length) + { + errors.Add(ErrorSeverity.Error, "Path is not a compatible account derivation path."); + } + } + else { errors.Add(ErrorSeverity.Error, "Path is not a valid derivation path."); } diff --git a/WalletWasabi.Gui/UiConfig.cs b/WalletWasabi.Gui/UiConfig.cs index 5963b39a0fe..89b64747e35 100644 --- a/WalletWasabi.Gui/UiConfig.cs +++ b/WalletWasabi.Gui/UiConfig.cs @@ -39,7 +39,7 @@ public UiConfig(string filePath) : base(filePath) [JsonProperty(PropertyName = "WindowState")] [JsonConverter(typeof(WindowStateAfterStartJsonConverter))] - public WindowState WindowState { get; internal set; } = WindowState.Maximized; + public WindowState WindowState { get; internal set; } = WindowState.Normal; [DefaultValue(2)] [JsonProperty(PropertyName = "FeeTarget", DefaultValueHandling = DefaultValueHandling.Populate)] @@ -111,6 +111,6 @@ public string LockScreenPinHash [JsonProperty(PropertyName = "HistoryTabViewSortingPreference")] [JsonConverter(typeof(SortingPreferenceJsonConverter))] - public SortingPreference HistoryTabViewSortingPreference { get; internal set; } = new SortingPreference(SortOrder.Increasing, "Date"); + public SortingPreference HistoryTabViewSortingPreference { get; internal set; } = new SortingPreference(SortOrder.Decreasing, "Date"); } } diff --git a/WalletWasabi.Gui/ViewModels/StatusBarViewModel.cs b/WalletWasabi.Gui/ViewModels/StatusBarViewModel.cs index 79074e8c553..1b8ac1384d8 100644 --- a/WalletWasabi.Gui/ViewModels/StatusBarViewModel.cs +++ b/WalletWasabi.Gui/ViewModels/StatusBarViewModel.cs @@ -47,7 +47,8 @@ public class StatusBarViewModel : ViewModelBase private TorStatus _tor; private int _peers; private ObservableAsPropertyHelper _filtersLeft; - private string _btcPrice; + private string _exchangeRate; + private bool _isExchangeRateAvailable; private ObservableAsPropertyHelper _status; private bool _downloadingBlock; @@ -62,7 +63,8 @@ public StatusBarViewModel() UseTor = false; Tor = TorStatus.NotRunning; Peers = 0; - BtcPrice = "$0"; + ExchangeRate = ""; + IsExchangeRateAvailable = false; ActiveStatuses = new StatusSet(); } @@ -128,10 +130,16 @@ public bool CriticalUpdateAvailable set => this.RaiseAndSetIfChanged(ref _criticalUpdateAvailable, value); } - public string BtcPrice + public string ExchangeRate { - get => _btcPrice; - set => this.RaiseAndSetIfChanged(ref _btcPrice, value); + get => _exchangeRate; + set => this.RaiseAndSetIfChanged(ref _exchangeRate, value); + } + + public bool IsExchangeRateAvailable + { + get => _isExchangeRateAvailable; + set => this.RaiseAndSetIfChanged(ref _isExchangeRateAvailable, value); } public string Status => _status?.Value ?? "Loading..."; @@ -253,7 +261,13 @@ public void Initialize(NodesCollection nodes, WasabiSynchronizer synchronizer) Synchronizer.WhenAnyValue(x => x.UsdExchangeRate) .ObserveOn(RxApp.MainThreadScheduler) - .Subscribe(usd => BtcPrice = $"${(long)usd}") + .Subscribe(usd => ExchangeRate = $"${(long)usd}") + .DisposeWith(Disposables); + + Synchronizer.WhenAnyValue(x => x.UsdExchangeRate) + .Select(x => x != default) + .ObserveOn(RxApp.MainThreadScheduler) + .Subscribe(x => IsExchangeRateAvailable = x) .DisposeWith(Disposables); if (rpcMonitor is { }) diff --git a/WalletWasabi.Gui/packages.lock.json b/WalletWasabi.Gui/packages.lock.json index 77b27bd2cad..b31cabfa95e 100644 --- a/WalletWasabi.Gui/packages.lock.json +++ b/WalletWasabi.Gui/packages.lock.json @@ -623,17 +623,17 @@ }, "NBitcoin": { "type": "Transitive", - "resolved": "5.0.47", - "contentHash": "fjEOFg2syu1AC2Q6NCeZ8GmwpDGtUeRTvbgiRyVQflm8NVSR/6X2mrFRu+KG/Q+77eq9c5K5ip081cnpuK9d4w==", + "resolved": "5.0.81", + "contentHash": "sBOvupELGlaw5mr4sNW0q8kwc6ALlYaG6telvebDLU7+9DYYYaPiHS1L8gPIa4BKpSO/9STQkMRbKmW7DcAUfQ==", "dependencies": { "Microsoft.Extensions.Logging.Abstractions": "1.0.0", - "Newtonsoft.Json": "11.0.1" + "Newtonsoft.Json": "11.0.2" } }, "NBitcoin.Secp256k1": { "type": "Transitive", - "resolved": "1.0.3", - "contentHash": "TCRUf7C44H/Hy42Ad1g0Dt83EfEH0l+4OuDhnWrzVsPBNiM6s6YRHnHYT+0dxGZKxD0CgdCTZ5f9Z2Ml1RRGbw==" + "resolved": "1.0.10", + "contentHash": "+CbOOtba1tv4p0G8uKRmwH4he5LXNtqfxdIrDi0RcVViR7HRTbaoDE7tJ7cAkg7pxuiNHGkpvtn+rFgkPxYgYw==" }, "NETStandard.Library": { "type": "Transitive", @@ -2335,10 +2335,8 @@ "Microsoft.Extensions.Caching.Memory": "3.1.20", "Microsoft.Extensions.Hosting.Abstractions": "3.1.6", "Microsoft.Win32.Registry": "4.7.0", - "NBitcoin": "5.0.47", - "NBitcoin.Secp256k1": "1.0.3", - "System.Collections.Immutable": "1.7.1", - "System.ComponentModel.Annotations": "4.7.0" + "NBitcoin": "5.0.81", + "NBitcoin.Secp256k1": "1.0.10" } } }, @@ -5640,4 +5638,4 @@ } } } -} \ No newline at end of file +} diff --git a/WalletWasabi.Packager/Program.cs b/WalletWasabi.Packager/Program.cs index bd68b1a8e33..be5f5ae85fd 100644 --- a/WalletWasabi.Packager/Program.cs +++ b/WalletWasabi.Packager/Program.cs @@ -66,20 +66,6 @@ private static void Main(string[] args) return; } - // If I want a list of up to date onions run it with '--getonions'. - if (IsGetOnionsMode(args)) - { - GetOnions(); - return; - } - - // If I want a list of up to date onions run it with '--getonions'. - if (IsReduceOnionsMode(args)) - { - ReduceOnions(); - return; - } - // Start with digest creation and return if only digest creation. CreateDigests(); @@ -114,63 +100,6 @@ private static void Main(string[] args) } } - private static void GetOnions() - { - WriteOnionsToConsole(null); - } - - private static void ReduceOnions() - { - var onionFile = Path.Combine(LibraryProjectDirectory, "OnionSeeds", "MainOnionSeeds.txt"); - var currentOnions = File.ReadAllLines(onionFile).ToHashSet(); - WriteOnionsToConsole(currentOnions); - } - - private static void WriteOnionsToConsole(HashSet currentOnions) - { - using var httpClient = new HttpClient(); - httpClient.BaseAddress = new Uri("https://bitnodes.21.co/api/v1/"); - - using var response = httpClient.GetAsync("snapshots/latest/", HttpCompletionOption.ResponseContentRead).GetAwaiter().GetResult(); - if (response.StatusCode != HttpStatusCode.OK) - { - throw new HttpRequestException(response.StatusCode.ToString()); - } - - var responseString = response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); - var json = (JObject)JsonConvert.DeserializeObject(responseString); - var onions = new List(); - foreach (JProperty node in json["nodes"]) - { - if (!node.Name.Contains(".onion")) - { - continue; - } - - var userAgent = ((JArray)node.Value)[1].ToString(); - - try - { - var verString = userAgent.Substring(userAgent.IndexOf("Satoshi:") + 8, 4); - var ver = new Version(verString); - bool addToResult = currentOnions is null || currentOnions.Contains(node.Name); - - if (ver >= new Version("0.16") && addToResult) - { - onions.Add(node.Name); - } - } - catch - { - } - } - - foreach (var onion in onions.OrderBy(x => x)) - { - Console.WriteLine(onion); - } - } - private static void CreateDigests() { var tempDir = "DigestTempDir"; @@ -180,24 +109,23 @@ private static void CreateDigests() var torDaemonsDir = Path.Combine(LibraryProjectDirectory, "TorDaemons"); string torWinZip = Path.Combine(torDaemonsDir, "tor-win64.zip"); IoHelpers.BetterExtractZipToDirectoryAsync(torWinZip, tempDir).GetAwaiter().GetResult(); - File.Move(Path.Combine(tempDir, "Tor", "tor.exe"), Path.Combine(tempDir, "TorWin")); + var winDigest = File.ReadAllBytes(Path.Combine(tempDir, "Tor", "tor.exe")); + IoHelpers.DeleteRecursivelyWithMagicDustAsync(tempDir).GetAwaiter().GetResult(); string torLinuxZip = Path.Combine(torDaemonsDir, "tor-linux64.zip"); IoHelpers.BetterExtractZipToDirectoryAsync(torLinuxZip, tempDir).GetAwaiter().GetResult(); - File.Move(Path.Combine(tempDir, "Tor", "tor"), Path.Combine(tempDir, "TorLin")); + var linDigest = File.ReadAllBytes(Path.Combine(tempDir, "Tor", "tor")); + IoHelpers.DeleteRecursivelyWithMagicDustAsync(tempDir).GetAwaiter().GetResult(); string torOsxZip = Path.Combine(torDaemonsDir, "tor-osx64.zip"); IoHelpers.BetterExtractZipToDirectoryAsync(torOsxZip, tempDir).GetAwaiter().GetResult(); - File.Move(Path.Combine(tempDir, "Tor", "tor.real"), Path.Combine(tempDir, "TorOsx")); + var macDigest = File.ReadAllBytes(Path.Combine(tempDir, "Tor", "tor")).Concat(File.ReadAllBytes(Path.Combine(tempDir, "Tor", "tor.real"))).ToArray(); - var tempDirInfo = new DirectoryInfo(tempDir); - var binaries = tempDirInfo.GetFiles(); Console.WriteLine("Digests:"); - foreach (var file in binaries) + foreach (var bytes in new[] { linDigest, macDigest, winDigest }) { - var filePath = file.FullName; - var hash = ByteHelpers.ToHex(IoHelpers.GetHashFile(filePath)).ToLowerInvariant(); - Console.WriteLine($"{file.Name}: {hash}"); + var hash = ByteHelpers.ToHex(IoHelpers.GetHashFile(bytes)).ToLowerInvariant(); + Console.WriteLine($"{hash}"); } IoHelpers.DeleteRecursivelyWithMagicDustAsync(tempDir).GetAwaiter().GetResult(); @@ -267,44 +195,6 @@ private static bool IsOnlyCreateDigestsMode(string[] args) return onlyCreateDigests; } - private static bool IsGetOnionsMode(string[] args) - { - bool getOnions = false; - if (args != null) - { - foreach (var arg in args) - { - if (arg.Trim().TrimStart('-').Equals("getonions", StringComparison.OrdinalIgnoreCase) - || arg.Trim().TrimStart('-').Equals("getonion", StringComparison.OrdinalIgnoreCase)) - { - getOnions = true; - break; - } - } - } - - return getOnions; - } - - private static bool IsReduceOnionsMode(string[] args) - { - bool getOnions = false; - if (args != null) - { - foreach (var arg in args) - { - if (arg.Trim().TrimStart('-').Equals("reduceonions", StringComparison.OrdinalIgnoreCase) - || arg.Trim().TrimStart('-').Equals("reduceonion", StringComparison.OrdinalIgnoreCase)) - { - getOnions = true; - break; - } - } - } - - return getOnions; - } - private static void RestoreProgramCs() { using var process = Process.Start(new ProcessStartInfo @@ -532,7 +422,7 @@ private static void Publish() foreach (var file in torFolder.EnumerateFiles()) { - if (!file.Name.Contains("data", StringComparison.OrdinalIgnoreCase) && !file.Name.Contains(toNotRemove, StringComparison.OrdinalIgnoreCase)) + if (!file.Name.Contains("data", StringComparison.OrdinalIgnoreCase) && !file.Name.Contains("digest", StringComparison.OrdinalIgnoreCase) && !file.Name.Contains(toNotRemove, StringComparison.OrdinalIgnoreCase)) { File.Delete(file.FullName); } diff --git a/WalletWasabi.Packager/packages.lock.json b/WalletWasabi.Packager/packages.lock.json index 70bdcdbc659..479f4b25de5 100644 --- a/WalletWasabi.Packager/packages.lock.json +++ b/WalletWasabi.Packager/packages.lock.json @@ -2,141 +2,248 @@ "version": 1, "dependencies": { ".NETCoreApp,Version=v3.1": { - "Microsoft.AspNetCore.WebUtilities": { + "Microsoft.AspNetCore.JsonPatch": { "type": "Transitive", - "resolved": "2.2.0", - "contentHash": "9ErxAAKaDzxXASB/b5uLEkLgUWv1QbeVxyJYEHQwMaxXOeFFVkQxiq8RyfVcifLU7NR0QY0p3acqx4ZpYfhHDg==", + "resolved": "3.1.1", + "contentHash": "Y2hwnbYzA8nmRH3+eTXtG+HP7rkMSLcqcLh5vfoN/J3zcmYb7vMtRauSDT9GO85JGwk+blNiCDXEou8Dj2TR4g==", "dependencies": { - "Microsoft.Net.Http.Headers": "2.2.0", - "System.Text.Encodings.Web": "4.5.0" + "Microsoft.CSharp": "4.7.0", + "Newtonsoft.Json": "12.0.2" } }, - "Microsoft.Extensions.Caching.Abstractions": { + "Microsoft.AspNetCore.Mvc.NewtonsoftJson": { "type": "Transitive", - "resolved": "3.1.20", - "contentHash": "Hg/mDuSQGBSeJNENWA/rTLMS4d6i8g9Eu08YQi3PS4r3/uKzw4FY4VgssyYxxILOhkGmwdzOqunsFXlrIlkvpA==", + "resolved": "3.1.1", + "contentHash": "t8vDVyivm/rnWvzvmVKGJUf7w8Mz1C4T3qnPAm0WyEU6LRt4WdLu4k1g8jVQ4qZTR7NDzv2DR0F2VSjZvkQdtQ==", "dependencies": { - "Microsoft.Extensions.Primitives": "3.1.20" + "Microsoft.AspNetCore.JsonPatch": "3.1.1", + "Newtonsoft.Json": "12.0.2", + "Newtonsoft.Json.Bson": "1.0.2" } }, - "Microsoft.Extensions.Caching.Memory": { + "Microsoft.CSharp": { "type": "Transitive", - "resolved": "3.1.20", - "contentHash": "B6EBjmeCGW6y+OoJAow6PqnZ/YF2ujkeDhAXvzkJqIJY1FsoZSb7c3j19vL1qt9HwynzvXAAi5jCcLh1FU1BdA==", + "resolved": "4.7.0", + "contentHash": "pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA==" + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "Transitive", + "resolved": "1.0.0", + "contentHash": "wHT6oY50q36mAXBRKtFaB7u07WxKC5u2M8fi3PqHOOnHyUo9gD0u1TlCNR8UObHQxKMYwqlgI8TLcErpt29n8A==", + "dependencies": { + "System.Collections": "4.0.11", + "System.Collections.Concurrent": "4.0.12", + "System.Diagnostics.Debug": "4.0.11", + "System.Globalization": "4.0.11", + "System.Linq": "4.1.0", + "System.Reflection": "4.1.0", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime.Extensions": "4.1.0", + "System.Runtime.InteropServices": "4.1.0" + } + }, + "Microsoft.NETCore.Platforms": { + "type": "Transitive", + "resolved": "3.1.0", + "contentHash": "z7aeg8oHln2CuNulfhiLYxCVMPEwBl3rzicjvIX+4sUuCwvXw5oXQEtbiU2c0z4qYL5L3Kmx0mMA/+t/SbY67w==" + }, + "Microsoft.NETCore.Targets": { + "type": "Transitive", + "resolved": "1.0.1", + "contentHash": "rkn+fKobF/cbWfnnfBOQHKVKIOpxMZBvlSHkqDWgBpwGDcLRduvs3D9OLGeV6GWGvVwNlVi2CBbTjuPmtHvyNw==" + }, + "Microsoft.Win32.Registry": { + "type": "Transitive", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "Microsoft.Extensions.Caching.Abstractions": "3.1.20", - "Microsoft.Extensions.DependencyInjection.Abstractions": "3.1.20", - "Microsoft.Extensions.Logging.Abstractions": "3.1.20", - "Microsoft.Extensions.Options": "3.1.20" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, - "Microsoft.Extensions.Configuration.Abstractions": { + "NBitcoin": { "type": "Transitive", - "resolved": "3.1.6", - "contentHash": "Z7QAA7XFvYuGZCKsfzetdIq0Vj1ngiIzCBBqerlQMhewx1ZFLXHuvKx9xAIWeZqhuQv69VmFs3vO0Af9FDAa9Q==", + "resolved": "5.0.81", + "contentHash": "sBOvupELGlaw5mr4sNW0q8kwc6ALlYaG6telvebDLU7+9DYYYaPiHS1L8gPIa4BKpSO/9STQkMRbKmW7DcAUfQ==", "dependencies": { - "Microsoft.Extensions.Primitives": "3.1.6" + "Microsoft.Extensions.Logging.Abstractions": "1.0.0", + "Newtonsoft.Json": "11.0.2" } }, - "Microsoft.Extensions.DependencyInjection.Abstractions": { + "NBitcoin.Secp256k1": { + "type": "Transitive", + "resolved": "1.0.10", + "contentHash": "+CbOOtba1tv4p0G8uKRmwH4he5LXNtqfxdIrDi0RcVViR7HRTbaoDE7tJ7cAkg7pxuiNHGkpvtn+rFgkPxYgYw==" + }, + "Newtonsoft.Json": { "type": "Transitive", - "resolved": "3.1.20", - "contentHash": "a2axLm7TfsB6rELiYDp7qx0S64h1FCFAFGz0WnPWgyshpvLWYM/XKwLHIPqiXuhtEp9kT4qBXRYsXMe6ZrxX0A==" + "resolved": "12.0.2", + "contentHash": "rTK0s2EKlfHsQsH6Yx2smvcTCeyoDNgCW7FEYyV01drPlh2T243PR2DiDXqtC5N4GDm4Ma/lkxfW5a/4793vbA==" }, - "Microsoft.Extensions.FileProviders.Abstractions": { + "Newtonsoft.Json.Bson": { "type": "Transitive", - "resolved": "3.1.6", - "contentHash": "HjEWm0GNHeN8ykVGn2DCebBEdZYL9CVypVAysaDHfjwiRT1d8nRhYGH8ua0mheE1TIep5+86O2Ft0UcbuWyghg==", + "resolved": "1.0.2", + "contentHash": "QYFyxhaABwmq3p/21VrZNYvCg3DaEoN/wUuw5nmfAf0X3HLjgupwhkEWdgfb9nvGAUIv3osmZoD3kKl4jxEmYQ==", "dependencies": { - "Microsoft.Extensions.Primitives": "3.1.6" + "Newtonsoft.Json": "12.0.1" } }, - "Microsoft.Extensions.Hosting.Abstractions": { + "System.Collections": { "type": "Transitive", - "resolved": "3.1.6", - "contentHash": "CtXVuuBcKQXmNQm6a/wGwKkYxLNsIp7TrL8+XPld6aFUisOkkBHTm3EIm8vwKlHRI8xLONgfgILXPXREGaBBwQ==", + "resolved": "4.0.11", + "contentHash": "YUJGz6eFKqS0V//mLt25vFGrrCvOnsXjlvFQs+KimpwNxug9x0Pzy4PlFMU3Q2IzqAa9G2L4LsK3+9vCBK7oTg==", "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "3.1.6", - "Microsoft.Extensions.DependencyInjection.Abstractions": "3.1.6", - "Microsoft.Extensions.FileProviders.Abstractions": "3.1.6", - "Microsoft.Extensions.Logging.Abstractions": "3.1.6" + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0" } }, - "Microsoft.Extensions.Logging.Abstractions": { + "System.Collections.Concurrent": { "type": "Transitive", - "resolved": "3.1.20", - "contentHash": "pejtJ+FM3tRm9Ssy9VO1PMkxlpkwbO+iQmK/Ot9DqgW3zjeLSQg3bEPI6klU70yoRSDwpiI8E71RdzU+vn/bTQ==" + "resolved": "4.0.12", + "contentHash": "2gBcbb3drMLgxlI0fBfxMA31ec6AEyYCHygGse4vxceJan8mRIWeKJ24BFzN7+bi/NFTgdIgufzb94LWO5EERQ==", + "dependencies": { + "System.Collections": "4.0.11", + "System.Diagnostics.Debug": "4.0.11", + "System.Diagnostics.Tracing": "4.1.0", + "System.Globalization": "4.0.11", + "System.Reflection": "4.1.0", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Extensions": "4.1.0", + "System.Threading": "4.0.11", + "System.Threading.Tasks": "4.0.11" + } }, - "Microsoft.Extensions.Options": { + "System.Diagnostics.Debug": { "type": "Transitive", - "resolved": "3.1.20", - "contentHash": "K5h3xUrYP8mbGZeGAm/vcWjol2wBh2V1vV+Vz02DCKlZ/99Y8ecKJwdpH+elfdqcEFXy76jk+I1nBsmhPKeCgw==", + "resolved": "4.0.11", + "contentHash": "w5U95fVKHY4G8ASs/K5iK3J5LY+/dLFd4vKejsnI/ZhBsWS9hQakfx3Zr7lRWKg4tAw9r4iktyvsTagWkqYCiw==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "3.1.20", - "Microsoft.Extensions.Primitives": "3.1.20" + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0" } }, - "Microsoft.Extensions.Primitives": { + "System.Diagnostics.Tracing": { "type": "Transitive", - "resolved": "3.1.20", - "contentHash": "RHHWUHzW8y+dyNBIBmo2EQbpCC6xFQcFMpLhNcpzw3zP0rxJdhmTTdy5eXvhlkNi3vqM4Af5Qqb5xgYwqaoaJQ==" + "resolved": "4.1.0", + "contentHash": "vDN1PoMZCkkdNjvZLql592oYJZgS7URcJzJ7bxeBgGtx5UtR5leNm49VmfHGqIffX4FKacHbI3H6UyNSHQknBg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0" + } }, - "Microsoft.Net.Http.Headers": { + "System.Globalization": { "type": "Transitive", - "resolved": "2.2.0", - "contentHash": "iZNkjYqlo8sIOI0bQfpsSoMTmB/kyvmV2h225ihyZT33aTp48ZpF6qYnXxzSXmHt8DpBAwBTX+1s1UFLbYfZKg==", + "resolved": "4.0.11", + "contentHash": "B95h0YLEL2oSnwF/XjqSWKnwKOy/01VWkNlsCeMTFJLLabflpGV26nK164eRs5GiaRSBGpOxQ3pKoSnnyZN5pg==", "dependencies": { - "Microsoft.Extensions.Primitives": "2.2.0", - "System.Buffers": "4.5.0" + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0" } }, - "Microsoft.NETCore.Platforms": { + "System.IO": { "type": "Transitive", - "resolved": "3.1.0", - "contentHash": "z7aeg8oHln2CuNulfhiLYxCVMPEwBl3rzicjvIX+4sUuCwvXw5oXQEtbiU2c0z4qYL5L3Kmx0mMA/+t/SbY67w==" + "resolved": "4.1.0", + "contentHash": "3KlTJceQc3gnGIaHZ7UBZO26SHL1SHE4ddrmiwumFnId+CEHP+O8r386tZKaE6zlk5/mF8vifMBzHj9SaXN+mQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0", + "System.Text.Encoding": "4.0.11", + "System.Threading.Tasks": "4.0.11" + } }, - "Microsoft.Win32.Registry": { + "System.Linq": { "type": "Transitive", - "resolved": "4.7.0", - "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", + "resolved": "4.1.0", + "contentHash": "bQ0iYFOQI0nuTnt+NQADns6ucV4DUvMdwN6CbkB1yj8i7arTGiTN5eok1kQwdnnNWSDZfIUySQY+J3d5KjWn0g==", "dependencies": { - "System.Security.AccessControl": "4.7.0", - "System.Security.Principal.Windows": "4.7.0" + "System.Collections": "4.0.11", + "System.Diagnostics.Debug": "4.0.11", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Extensions": "4.1.0" } }, - "NBitcoin": { + "System.Reflection": { "type": "Transitive", - "resolved": "5.0.47", - "contentHash": "fjEOFg2syu1AC2Q6NCeZ8GmwpDGtUeRTvbgiRyVQflm8NVSR/6X2mrFRu+KG/Q+77eq9c5K5ip081cnpuK9d4w==", + "resolved": "4.1.0", + "contentHash": "JCKANJ0TI7kzoQzuwB/OoJANy1Lg338B6+JVacPl4TpUwi3cReg3nMLplMq2uqYfHFQpKIlHAUVAJlImZz/4ng==", "dependencies": { - "Microsoft.Extensions.Logging.Abstractions": "1.0.0", - "Newtonsoft.Json": "11.0.1" + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.IO": "4.1.0", + "System.Reflection.Primitives": "4.0.1", + "System.Runtime": "4.1.0" } }, - "NBitcoin.Secp256k1": { + "System.Reflection.Primitives": { "type": "Transitive", - "resolved": "1.0.3", - "contentHash": "TCRUf7C44H/Hy42Ad1g0Dt83EfEH0l+4OuDhnWrzVsPBNiM6s6YRHnHYT+0dxGZKxD0CgdCTZ5f9Z2Ml1RRGbw==" + "resolved": "4.0.1", + "contentHash": "4inTox4wTBaDhB7V3mPvp9XlCbeGYWVEM9/fXALd52vNEAVisc1BoVWQPuUuD0Ga//dNbA/WeMy9u9mzLxGTHQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0" + } }, - "Newtonsoft.Json": { + "System.Resources.ResourceManager": { + "type": "Transitive", + "resolved": "4.0.1", + "contentHash": "TxwVeUNoTgUOdQ09gfTjvW411MF+w9MBYL7AtNVc+HtBCFlutPLhUCdZjNkjbhj3bNQWMdHboF0KIWEOjJssbA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Globalization": "4.0.11", + "System.Reflection": "4.1.0", + "System.Runtime": "4.1.0" + } + }, + "System.Runtime": { "type": "Transitive", - "resolved": "11.0.1", - "contentHash": "pNN4l+J6LlpIvHOeNdXlwxv39NPJ2B5klz+Rd2UQZIx30Squ5oND1Yy3wEAUoKn0GPUj6Yxt9lxlYWQqfZcvKg==" + "resolved": "4.1.0", + "contentHash": "v6c/4Yaa9uWsq+JMhnOFewrYkgdNHNG2eMKuNqRn8P733rNXeRCGvV5FkkjBXn2dbVkPXOsO0xjsEeM1q2zC0g==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1" + } }, - "System.Buffers": { + "System.Runtime.Extensions": { "type": "Transitive", - "resolved": "4.5.0", - "contentHash": "pL2ChpaRRWI/p4LXyy4RgeWlYF2sgfj/pnVMvBqwNFr5cXg7CXNnWZWxrOONLg8VGdFB8oB+EG2Qw4MLgTOe+A==" + "resolved": "4.1.0", + "contentHash": "CUOHjTT/vgP0qGW22U4/hDlOqXmcPq5YicBaXdUR2UiUoLwBT+olO6we4DVbq57jeX5uXH2uerVZhf0qGj+sVQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0" + } }, - "System.Collections.Immutable": { + "System.Runtime.Handles": { "type": "Transitive", - "resolved": "1.7.1", - "contentHash": "B43Zsz5EfMwyEbnObwRxW5u85fzJma3lrDeGcSAV1qkhSRTNY5uXAByTn9h9ddNdhM+4/YoLc/CI43umjwIl9Q==" + "resolved": "4.0.1", + "contentHash": "nCJvEKguXEvk2ymk1gqj625vVnlK3/xdGzx0vOKicQkoquaTBJTP13AIYkocSUwHCLNBwUbXTqTWGDxBTWpt7g==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0" + } }, - "System.ComponentModel.Annotations": { + "System.Runtime.InteropServices": { "type": "Transitive", - "resolved": "4.7.0", - "contentHash": "0YFqjhp/mYkDGpU0Ye1GjE53HMp9UVfGN7seGpAMttAC0C40v5gw598jCgpbBLMmCo0E5YRLBv5Z2doypO49ZQ==" + "resolved": "4.1.0", + "contentHash": "16eu3kjHS633yYdkjwShDHZLRNMKVi/s0bY8ODiqJ2RfMhDMAwxZaUaWVnZ2P71kr/or+X9o/xFWtNqz8ivieQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Reflection": "4.1.0", + "System.Reflection.Primitives": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Handles": "4.0.1" + } }, "System.Security.AccessControl": { "type": "Transitive", @@ -152,22 +259,42 @@ "resolved": "4.7.0", "contentHash": "ojD0PX0XhneCsUbAZVKdb7h/70vyYMDYs85lwEI+LngEONe/17A0cFaRFqZU+sOEidcVswYWikYOQ9PPfjlbtQ==" }, - "System.Text.Encodings.Web": { + "System.Text.Encoding": { "type": "Transitive", - "resolved": "4.5.0", - "contentHash": "Xg4G4Indi4dqP1iuAiMSwpiWS54ZghzR644OtsRCm/m/lBMG8dUBhLVN7hLm8NNrNTR+iGbshCPTwrvxZPlm4g==" + "resolved": "4.0.11", + "contentHash": "U3gGeMlDZXxCEiY4DwVLSacg+DFWCvoiX+JThA/rvw37Sqrku7sEFeVBBBMBnfB6FeZHsyDx85HlKL19x0HtZA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0" + } + }, + "System.Threading": { + "type": "Transitive", + "resolved": "4.0.11", + "contentHash": "N+3xqIcg3VDKyjwwCGaZ9HawG9aC6cSDI+s7ROma310GQo8vilFZa86hqKppwTHleR/G0sfOzhvgnUxWCR/DrQ==", + "dependencies": { + "System.Runtime": "4.1.0", + "System.Threading.Tasks": "4.0.11" + } + }, + "System.Threading.Tasks": { + "type": "Transitive", + "resolved": "4.0.11", + "contentHash": "k1S4Gc6IGwtHGT8188RSeGaX86Qw/wnrgNLshJvsdNUOPP9etMmo8S07c+UlOAx4K/xLuN9ivA1bD0LVurtIxQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0" + } }, "walletwasabi": { "type": "Project", "dependencies": { - "Microsoft.AspNetCore.WebUtilities": "2.2.0", - "Microsoft.Extensions.Caching.Memory": "3.1.20", - "Microsoft.Extensions.Hosting.Abstractions": "3.1.6", + "Microsoft.AspNetCore.Mvc.NewtonsoftJson": "3.1.1", "Microsoft.Win32.Registry": "4.7.0", - "NBitcoin": "5.0.47", - "NBitcoin.Secp256k1": "1.0.3", - "System.Collections.Immutable": "1.7.1", - "System.ComponentModel.Annotations": "4.7.0" + "NBitcoin": "5.0.81", + "NBitcoin.Secp256k1": "1.0.10" } } }, @@ -181,6 +308,266 @@ "System.Security.Principal.Windows": "4.7.0" } }, + "runtime.any.System.Collections": { + "type": "Transitive", + "resolved": "4.0.11", + "contentHash": "MTBT/hu37Dm2042H1JjWSaMd8w+oPJ4ZWAbDNeLzC4ZHdqwHloP07KvD6+4VbwipDqY5obfFFy90mZYCaPDh5Q==", + "dependencies": { + "System.Runtime": "4.1.0" + } + }, + "runtime.any.System.Diagnostics.Tracing": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "x7VLOl/v504jX97YEMePamZRHA3cJPOFY/xLw9pgjDr0Q3IQIZ+0K4oiKKtQrfMYSvOAntkzw+EvvQ+OWGRL9w==" + }, + "runtime.any.System.Globalization": { + "type": "Transitive", + "resolved": "4.0.11", + "contentHash": "cjJ3+b83Tpf02AIc5FkGj1vzY68RnsVHiGLrOCc5n7gpNVg1JnZrt1mcY99ykQ/wr3nCdvSP2pYvdxbYsxZdlA==" + }, + "runtime.any.System.IO": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "sC7zKVdhYQEtrREKBJf4zkUwNdi6fsbkzrhJLDIAxIxD+YA5PABAQJps13zxpA1Ke3AgzOA9551JDymAfmRuTg==" + }, + "runtime.any.System.Reflection": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "eKq6/GprEINYbugjWf2V9cjkyuAH/y+Raed28PJQ35zd30oR/pvKEHNN8JbPAgzYpI09TCd1yuhXN/Rb8PM8GA==" + }, + "runtime.any.System.Reflection.Primitives": { + "type": "Transitive", + "resolved": "4.0.1", + "contentHash": "oKs78h11WDhCGFNpxT26IqL8Oo8OBzr6YOW0WG+R14FGaB/WDM5UHiK/jr6dipdnO8Wxlg/U48ka6uaPM6l53w==" + }, + "runtime.any.System.Resources.ResourceManager": { + "type": "Transitive", + "resolved": "4.0.1", + "contentHash": "hes7WFTOERydB/hLGmLj66NbK7I2AnjLHEeTpf7EmPZOIrRWeuC1dPoFYC9XRVIVzfCcOZI7oXM7KXe4vakt9Q==" + }, + "runtime.any.System.Runtime": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "0QVLwEGXROl0Trt2XosEjly9uqXcjHKStoZyZG9twJYFZJqq2JJXcBMXl/fnyQAgYEEODV8lUsU+t7NCCY0nUQ==", + "dependencies": { + "System.Private.Uri": "4.0.1" + } + }, + "runtime.any.System.Runtime.Handles": { + "type": "Transitive", + "resolved": "4.0.1", + "contentHash": "MZ5fVmAE/3S11wt3hPfn3RsAHppj5gUz+VZuLQkRjLCMSlX0krOI601IZsMWc3CoxUb+wMt3gZVb/mEjblw6Mg==" + }, + "runtime.any.System.Runtime.InteropServices": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "gmibdZ9x/eB6hf5le33DWLCQbhcIUD2vqoc0tBgqSUWlB8YjEzVJXyTPDO+ypKLlL90Kv3ZDrK7yPCNqcyhqCA==" + }, + "runtime.any.System.Text.Encoding": { + "type": "Transitive", + "resolved": "4.0.11", + "contentHash": "uweRMRDD4O8Iy8m4h1cJvoFIHNCzHMpipuxkRNAMML6EMzAhDCQTjgvRwki7PlUg8RGY1ctXnBZjT1rXvMZuRw==" + }, + "runtime.any.System.Threading.Tasks": { + "type": "Transitive", + "resolved": "4.0.11", + "contentHash": "CEvWO0IwtdCAsmCb9aAl59psy0hzx+whYh4DzbjNb0GsQmxw/G7bZEcrBtE8c9QupNVbu87c2xaMi6p4r1bpjA==" + }, + "runtime.native.System": { + "type": "Transitive", + "resolved": "4.0.0", + "contentHash": "QfS/nQI7k/BLgmLrw7qm7YBoULEvgWnPI+cYsbfCVFTW8Aj+i8JhccxcFMu1RWms0YZzF+UHguNBK4Qn89e2Sg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1" + } + }, + "runtime.native.System.Security.Cryptography": { + "type": "Transitive", + "resolved": "4.0.0", + "contentHash": "2CQK0jmO6Eu7ZeMgD+LOFbNJSXHFVQbCJJkEyEwowh1SCgYnrn9W9RykMfpeeVGw7h4IBvYikzpGUlmZTUafJw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1" + } + }, + "runtime.unix.System.Diagnostics.Debug": { + "type": "Transitive", + "resolved": "4.0.11", + "contentHash": "dGIYWbyqSlMlZrsqtU/TdvVNp8lieqowdGBVMi6nFTIiCqrL+RbdiJORguexXNjHtFZR30eE6zPWGxuL60NYFw==", + "dependencies": { + "runtime.native.System": "4.0.0" + } + }, + "runtime.unix.System.Private.Uri": { + "type": "Transitive", + "resolved": "4.0.1", + "contentHash": "m+7TLWWw4cA44vGxcKpMdV2Lgx6HWOe5rUb5RIADE04S6fJNEwXO6u+KY7oWFJQYn5644NyhSxB9oV28fF94NQ==", + "dependencies": { + "runtime.native.System": "4.0.0" + } + }, + "runtime.unix.System.Runtime.Extensions": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "ouVt2t9k22LcC9HeNX4mu3Ebvp1h+IPKaYiU3tDtOW9YcMR62XQyHsPq5BjBjMHuxjBRL5Hz+BwhSdrY6HjacA==", + "dependencies": { + "System.Private.Uri": "4.0.1", + "runtime.native.System": "4.0.0", + "runtime.native.System.Security.Cryptography": "4.0.0" + } + }, + "System.Collections": { + "type": "Transitive", + "resolved": "4.0.11", + "contentHash": "YUJGz6eFKqS0V//mLt25vFGrrCvOnsXjlvFQs+KimpwNxug9x0Pzy4PlFMU3Q2IzqAa9G2L4LsK3+9vCBK7oTg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0", + "runtime.any.System.Collections": "4.0.11" + } + }, + "System.Diagnostics.Debug": { + "type": "Transitive", + "resolved": "4.0.11", + "contentHash": "w5U95fVKHY4G8ASs/K5iK3J5LY+/dLFd4vKejsnI/ZhBsWS9hQakfx3Zr7lRWKg4tAw9r4iktyvsTagWkqYCiw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0", + "runtime.unix.System.Diagnostics.Debug": "4.0.11" + } + }, + "System.Diagnostics.Tracing": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "vDN1PoMZCkkdNjvZLql592oYJZgS7URcJzJ7bxeBgGtx5UtR5leNm49VmfHGqIffX4FKacHbI3H6UyNSHQknBg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0", + "runtime.any.System.Diagnostics.Tracing": "4.1.0" + } + }, + "System.Globalization": { + "type": "Transitive", + "resolved": "4.0.11", + "contentHash": "B95h0YLEL2oSnwF/XjqSWKnwKOy/01VWkNlsCeMTFJLLabflpGV26nK164eRs5GiaRSBGpOxQ3pKoSnnyZN5pg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0", + "runtime.any.System.Globalization": "4.0.11" + } + }, + "System.IO": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "3KlTJceQc3gnGIaHZ7UBZO26SHL1SHE4ddrmiwumFnId+CEHP+O8r386tZKaE6zlk5/mF8vifMBzHj9SaXN+mQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0", + "System.Text.Encoding": "4.0.11", + "System.Threading.Tasks": "4.0.11", + "runtime.any.System.IO": "4.1.0" + } + }, + "System.Private.Uri": { + "type": "Transitive", + "resolved": "4.0.1", + "contentHash": "OltceAn9yyNf9LZIqvf80DhdRH55iVu1fxowdR79018w1CWIRNojUZBStsiRHvADeKI5pXcM9EftOFikBQh5AA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "runtime.unix.System.Private.Uri": "4.0.1" + } + }, + "System.Reflection": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "JCKANJ0TI7kzoQzuwB/OoJANy1Lg338B6+JVacPl4TpUwi3cReg3nMLplMq2uqYfHFQpKIlHAUVAJlImZz/4ng==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.IO": "4.1.0", + "System.Reflection.Primitives": "4.0.1", + "System.Runtime": "4.1.0", + "runtime.any.System.Reflection": "4.1.0" + } + }, + "System.Reflection.Primitives": { + "type": "Transitive", + "resolved": "4.0.1", + "contentHash": "4inTox4wTBaDhB7V3mPvp9XlCbeGYWVEM9/fXALd52vNEAVisc1BoVWQPuUuD0Ga//dNbA/WeMy9u9mzLxGTHQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0", + "runtime.any.System.Reflection.Primitives": "4.0.1" + } + }, + "System.Resources.ResourceManager": { + "type": "Transitive", + "resolved": "4.0.1", + "contentHash": "TxwVeUNoTgUOdQ09gfTjvW411MF+w9MBYL7AtNVc+HtBCFlutPLhUCdZjNkjbhj3bNQWMdHboF0KIWEOjJssbA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Globalization": "4.0.11", + "System.Reflection": "4.1.0", + "System.Runtime": "4.1.0", + "runtime.any.System.Resources.ResourceManager": "4.0.1" + } + }, + "System.Runtime": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "v6c/4Yaa9uWsq+JMhnOFewrYkgdNHNG2eMKuNqRn8P733rNXeRCGvV5FkkjBXn2dbVkPXOsO0xjsEeM1q2zC0g==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "runtime.any.System.Runtime": "4.1.0" + } + }, + "System.Runtime.Extensions": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "CUOHjTT/vgP0qGW22U4/hDlOqXmcPq5YicBaXdUR2UiUoLwBT+olO6we4DVbq57jeX5uXH2uerVZhf0qGj+sVQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0", + "runtime.unix.System.Runtime.Extensions": "4.1.0" + } + }, + "System.Runtime.Handles": { + "type": "Transitive", + "resolved": "4.0.1", + "contentHash": "nCJvEKguXEvk2ymk1gqj625vVnlK3/xdGzx0vOKicQkoquaTBJTP13AIYkocSUwHCLNBwUbXTqTWGDxBTWpt7g==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0", + "runtime.any.System.Runtime.Handles": "4.0.1" + } + }, + "System.Runtime.InteropServices": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "16eu3kjHS633yYdkjwShDHZLRNMKVi/s0bY8ODiqJ2RfMhDMAwxZaUaWVnZ2P71kr/or+X9o/xFWtNqz8ivieQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Reflection": "4.1.0", + "System.Reflection.Primitives": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Handles": "4.0.1", + "runtime.any.System.Runtime.InteropServices": "4.1.0" + } + }, "System.Security.AccessControl": { "type": "Transitive", "resolved": "4.7.0", @@ -194,6 +581,28 @@ "type": "Transitive", "resolved": "4.7.0", "contentHash": "ojD0PX0XhneCsUbAZVKdb7h/70vyYMDYs85lwEI+LngEONe/17A0cFaRFqZU+sOEidcVswYWikYOQ9PPfjlbtQ==" + }, + "System.Text.Encoding": { + "type": "Transitive", + "resolved": "4.0.11", + "contentHash": "U3gGeMlDZXxCEiY4DwVLSacg+DFWCvoiX+JThA/rvw37Sqrku7sEFeVBBBMBnfB6FeZHsyDx85HlKL19x0HtZA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0", + "runtime.any.System.Text.Encoding": "4.0.11" + } + }, + "System.Threading.Tasks": { + "type": "Transitive", + "resolved": "4.0.11", + "contentHash": "k1S4Gc6IGwtHGT8188RSeGaX86Qw/wnrgNLshJvsdNUOPP9etMmo8S07c+UlOAx4K/xLuN9ivA1bD0LVurtIxQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0", + "runtime.any.System.Threading.Tasks": "4.0.11" + } } }, ".NETCoreApp,Version=v3.1/osx-x64": { @@ -206,6 +615,266 @@ "System.Security.Principal.Windows": "4.7.0" } }, + "runtime.any.System.Collections": { + "type": "Transitive", + "resolved": "4.0.11", + "contentHash": "MTBT/hu37Dm2042H1JjWSaMd8w+oPJ4ZWAbDNeLzC4ZHdqwHloP07KvD6+4VbwipDqY5obfFFy90mZYCaPDh5Q==", + "dependencies": { + "System.Runtime": "4.1.0" + } + }, + "runtime.any.System.Diagnostics.Tracing": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "x7VLOl/v504jX97YEMePamZRHA3cJPOFY/xLw9pgjDr0Q3IQIZ+0K4oiKKtQrfMYSvOAntkzw+EvvQ+OWGRL9w==" + }, + "runtime.any.System.Globalization": { + "type": "Transitive", + "resolved": "4.0.11", + "contentHash": "cjJ3+b83Tpf02AIc5FkGj1vzY68RnsVHiGLrOCc5n7gpNVg1JnZrt1mcY99ykQ/wr3nCdvSP2pYvdxbYsxZdlA==" + }, + "runtime.any.System.IO": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "sC7zKVdhYQEtrREKBJf4zkUwNdi6fsbkzrhJLDIAxIxD+YA5PABAQJps13zxpA1Ke3AgzOA9551JDymAfmRuTg==" + }, + "runtime.any.System.Reflection": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "eKq6/GprEINYbugjWf2V9cjkyuAH/y+Raed28PJQ35zd30oR/pvKEHNN8JbPAgzYpI09TCd1yuhXN/Rb8PM8GA==" + }, + "runtime.any.System.Reflection.Primitives": { + "type": "Transitive", + "resolved": "4.0.1", + "contentHash": "oKs78h11WDhCGFNpxT26IqL8Oo8OBzr6YOW0WG+R14FGaB/WDM5UHiK/jr6dipdnO8Wxlg/U48ka6uaPM6l53w==" + }, + "runtime.any.System.Resources.ResourceManager": { + "type": "Transitive", + "resolved": "4.0.1", + "contentHash": "hes7WFTOERydB/hLGmLj66NbK7I2AnjLHEeTpf7EmPZOIrRWeuC1dPoFYC9XRVIVzfCcOZI7oXM7KXe4vakt9Q==" + }, + "runtime.any.System.Runtime": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "0QVLwEGXROl0Trt2XosEjly9uqXcjHKStoZyZG9twJYFZJqq2JJXcBMXl/fnyQAgYEEODV8lUsU+t7NCCY0nUQ==", + "dependencies": { + "System.Private.Uri": "4.0.1" + } + }, + "runtime.any.System.Runtime.Handles": { + "type": "Transitive", + "resolved": "4.0.1", + "contentHash": "MZ5fVmAE/3S11wt3hPfn3RsAHppj5gUz+VZuLQkRjLCMSlX0krOI601IZsMWc3CoxUb+wMt3gZVb/mEjblw6Mg==" + }, + "runtime.any.System.Runtime.InteropServices": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "gmibdZ9x/eB6hf5le33DWLCQbhcIUD2vqoc0tBgqSUWlB8YjEzVJXyTPDO+ypKLlL90Kv3ZDrK7yPCNqcyhqCA==" + }, + "runtime.any.System.Text.Encoding": { + "type": "Transitive", + "resolved": "4.0.11", + "contentHash": "uweRMRDD4O8Iy8m4h1cJvoFIHNCzHMpipuxkRNAMML6EMzAhDCQTjgvRwki7PlUg8RGY1ctXnBZjT1rXvMZuRw==" + }, + "runtime.any.System.Threading.Tasks": { + "type": "Transitive", + "resolved": "4.0.11", + "contentHash": "CEvWO0IwtdCAsmCb9aAl59psy0hzx+whYh4DzbjNb0GsQmxw/G7bZEcrBtE8c9QupNVbu87c2xaMi6p4r1bpjA==" + }, + "runtime.native.System": { + "type": "Transitive", + "resolved": "4.0.0", + "contentHash": "QfS/nQI7k/BLgmLrw7qm7YBoULEvgWnPI+cYsbfCVFTW8Aj+i8JhccxcFMu1RWms0YZzF+UHguNBK4Qn89e2Sg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1" + } + }, + "runtime.native.System.Security.Cryptography": { + "type": "Transitive", + "resolved": "4.0.0", + "contentHash": "2CQK0jmO6Eu7ZeMgD+LOFbNJSXHFVQbCJJkEyEwowh1SCgYnrn9W9RykMfpeeVGw7h4IBvYikzpGUlmZTUafJw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1" + } + }, + "runtime.unix.System.Diagnostics.Debug": { + "type": "Transitive", + "resolved": "4.0.11", + "contentHash": "dGIYWbyqSlMlZrsqtU/TdvVNp8lieqowdGBVMi6nFTIiCqrL+RbdiJORguexXNjHtFZR30eE6zPWGxuL60NYFw==", + "dependencies": { + "runtime.native.System": "4.0.0" + } + }, + "runtime.unix.System.Private.Uri": { + "type": "Transitive", + "resolved": "4.0.1", + "contentHash": "m+7TLWWw4cA44vGxcKpMdV2Lgx6HWOe5rUb5RIADE04S6fJNEwXO6u+KY7oWFJQYn5644NyhSxB9oV28fF94NQ==", + "dependencies": { + "runtime.native.System": "4.0.0" + } + }, + "runtime.unix.System.Runtime.Extensions": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "ouVt2t9k22LcC9HeNX4mu3Ebvp1h+IPKaYiU3tDtOW9YcMR62XQyHsPq5BjBjMHuxjBRL5Hz+BwhSdrY6HjacA==", + "dependencies": { + "System.Private.Uri": "4.0.1", + "runtime.native.System": "4.0.0", + "runtime.native.System.Security.Cryptography": "4.0.0" + } + }, + "System.Collections": { + "type": "Transitive", + "resolved": "4.0.11", + "contentHash": "YUJGz6eFKqS0V//mLt25vFGrrCvOnsXjlvFQs+KimpwNxug9x0Pzy4PlFMU3Q2IzqAa9G2L4LsK3+9vCBK7oTg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0", + "runtime.any.System.Collections": "4.0.11" + } + }, + "System.Diagnostics.Debug": { + "type": "Transitive", + "resolved": "4.0.11", + "contentHash": "w5U95fVKHY4G8ASs/K5iK3J5LY+/dLFd4vKejsnI/ZhBsWS9hQakfx3Zr7lRWKg4tAw9r4iktyvsTagWkqYCiw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0", + "runtime.unix.System.Diagnostics.Debug": "4.0.11" + } + }, + "System.Diagnostics.Tracing": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "vDN1PoMZCkkdNjvZLql592oYJZgS7URcJzJ7bxeBgGtx5UtR5leNm49VmfHGqIffX4FKacHbI3H6UyNSHQknBg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0", + "runtime.any.System.Diagnostics.Tracing": "4.1.0" + } + }, + "System.Globalization": { + "type": "Transitive", + "resolved": "4.0.11", + "contentHash": "B95h0YLEL2oSnwF/XjqSWKnwKOy/01VWkNlsCeMTFJLLabflpGV26nK164eRs5GiaRSBGpOxQ3pKoSnnyZN5pg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0", + "runtime.any.System.Globalization": "4.0.11" + } + }, + "System.IO": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "3KlTJceQc3gnGIaHZ7UBZO26SHL1SHE4ddrmiwumFnId+CEHP+O8r386tZKaE6zlk5/mF8vifMBzHj9SaXN+mQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0", + "System.Text.Encoding": "4.0.11", + "System.Threading.Tasks": "4.0.11", + "runtime.any.System.IO": "4.1.0" + } + }, + "System.Private.Uri": { + "type": "Transitive", + "resolved": "4.0.1", + "contentHash": "OltceAn9yyNf9LZIqvf80DhdRH55iVu1fxowdR79018w1CWIRNojUZBStsiRHvADeKI5pXcM9EftOFikBQh5AA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "runtime.unix.System.Private.Uri": "4.0.1" + } + }, + "System.Reflection": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "JCKANJ0TI7kzoQzuwB/OoJANy1Lg338B6+JVacPl4TpUwi3cReg3nMLplMq2uqYfHFQpKIlHAUVAJlImZz/4ng==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.IO": "4.1.0", + "System.Reflection.Primitives": "4.0.1", + "System.Runtime": "4.1.0", + "runtime.any.System.Reflection": "4.1.0" + } + }, + "System.Reflection.Primitives": { + "type": "Transitive", + "resolved": "4.0.1", + "contentHash": "4inTox4wTBaDhB7V3mPvp9XlCbeGYWVEM9/fXALd52vNEAVisc1BoVWQPuUuD0Ga//dNbA/WeMy9u9mzLxGTHQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0", + "runtime.any.System.Reflection.Primitives": "4.0.1" + } + }, + "System.Resources.ResourceManager": { + "type": "Transitive", + "resolved": "4.0.1", + "contentHash": "TxwVeUNoTgUOdQ09gfTjvW411MF+w9MBYL7AtNVc+HtBCFlutPLhUCdZjNkjbhj3bNQWMdHboF0KIWEOjJssbA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Globalization": "4.0.11", + "System.Reflection": "4.1.0", + "System.Runtime": "4.1.0", + "runtime.any.System.Resources.ResourceManager": "4.0.1" + } + }, + "System.Runtime": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "v6c/4Yaa9uWsq+JMhnOFewrYkgdNHNG2eMKuNqRn8P733rNXeRCGvV5FkkjBXn2dbVkPXOsO0xjsEeM1q2zC0g==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "runtime.any.System.Runtime": "4.1.0" + } + }, + "System.Runtime.Extensions": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "CUOHjTT/vgP0qGW22U4/hDlOqXmcPq5YicBaXdUR2UiUoLwBT+olO6we4DVbq57jeX5uXH2uerVZhf0qGj+sVQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0", + "runtime.unix.System.Runtime.Extensions": "4.1.0" + } + }, + "System.Runtime.Handles": { + "type": "Transitive", + "resolved": "4.0.1", + "contentHash": "nCJvEKguXEvk2ymk1gqj625vVnlK3/xdGzx0vOKicQkoquaTBJTP13AIYkocSUwHCLNBwUbXTqTWGDxBTWpt7g==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0", + "runtime.any.System.Runtime.Handles": "4.0.1" + } + }, + "System.Runtime.InteropServices": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "16eu3kjHS633yYdkjwShDHZLRNMKVi/s0bY8ODiqJ2RfMhDMAwxZaUaWVnZ2P71kr/or+X9o/xFWtNqz8ivieQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Reflection": "4.1.0", + "System.Reflection.Primitives": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Handles": "4.0.1", + "runtime.any.System.Runtime.InteropServices": "4.1.0" + } + }, "System.Security.AccessControl": { "type": "Transitive", "resolved": "4.7.0", @@ -219,6 +888,28 @@ "type": "Transitive", "resolved": "4.7.0", "contentHash": "ojD0PX0XhneCsUbAZVKdb7h/70vyYMDYs85lwEI+LngEONe/17A0cFaRFqZU+sOEidcVswYWikYOQ9PPfjlbtQ==" + }, + "System.Text.Encoding": { + "type": "Transitive", + "resolved": "4.0.11", + "contentHash": "U3gGeMlDZXxCEiY4DwVLSacg+DFWCvoiX+JThA/rvw37Sqrku7sEFeVBBBMBnfB6FeZHsyDx85HlKL19x0HtZA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0", + "runtime.any.System.Text.Encoding": "4.0.11" + } + }, + "System.Threading.Tasks": { + "type": "Transitive", + "resolved": "4.0.11", + "contentHash": "k1S4Gc6IGwtHGT8188RSeGaX86Qw/wnrgNLshJvsdNUOPP9etMmo8S07c+UlOAx4K/xLuN9ivA1bD0LVurtIxQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0", + "runtime.any.System.Threading.Tasks": "4.0.11" + } } }, ".NETCoreApp,Version=v3.1/win7-x64": { @@ -231,6 +922,240 @@ "System.Security.Principal.Windows": "4.7.0" } }, + "runtime.any.System.Collections": { + "type": "Transitive", + "resolved": "4.0.11", + "contentHash": "MTBT/hu37Dm2042H1JjWSaMd8w+oPJ4ZWAbDNeLzC4ZHdqwHloP07KvD6+4VbwipDqY5obfFFy90mZYCaPDh5Q==", + "dependencies": { + "System.Runtime": "4.1.0" + } + }, + "runtime.any.System.Diagnostics.Tracing": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "x7VLOl/v504jX97YEMePamZRHA3cJPOFY/xLw9pgjDr0Q3IQIZ+0K4oiKKtQrfMYSvOAntkzw+EvvQ+OWGRL9w==" + }, + "runtime.any.System.Globalization": { + "type": "Transitive", + "resolved": "4.0.11", + "contentHash": "cjJ3+b83Tpf02AIc5FkGj1vzY68RnsVHiGLrOCc5n7gpNVg1JnZrt1mcY99ykQ/wr3nCdvSP2pYvdxbYsxZdlA==" + }, + "runtime.any.System.IO": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "sC7zKVdhYQEtrREKBJf4zkUwNdi6fsbkzrhJLDIAxIxD+YA5PABAQJps13zxpA1Ke3AgzOA9551JDymAfmRuTg==" + }, + "runtime.any.System.Reflection": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "eKq6/GprEINYbugjWf2V9cjkyuAH/y+Raed28PJQ35zd30oR/pvKEHNN8JbPAgzYpI09TCd1yuhXN/Rb8PM8GA==" + }, + "runtime.any.System.Reflection.Primitives": { + "type": "Transitive", + "resolved": "4.0.1", + "contentHash": "oKs78h11WDhCGFNpxT26IqL8Oo8OBzr6YOW0WG+R14FGaB/WDM5UHiK/jr6dipdnO8Wxlg/U48ka6uaPM6l53w==" + }, + "runtime.any.System.Resources.ResourceManager": { + "type": "Transitive", + "resolved": "4.0.1", + "contentHash": "hes7WFTOERydB/hLGmLj66NbK7I2AnjLHEeTpf7EmPZOIrRWeuC1dPoFYC9XRVIVzfCcOZI7oXM7KXe4vakt9Q==" + }, + "runtime.any.System.Runtime": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "0QVLwEGXROl0Trt2XosEjly9uqXcjHKStoZyZG9twJYFZJqq2JJXcBMXl/fnyQAgYEEODV8lUsU+t7NCCY0nUQ==", + "dependencies": { + "System.Private.Uri": "4.0.1" + } + }, + "runtime.any.System.Runtime.Handles": { + "type": "Transitive", + "resolved": "4.0.1", + "contentHash": "MZ5fVmAE/3S11wt3hPfn3RsAHppj5gUz+VZuLQkRjLCMSlX0krOI601IZsMWc3CoxUb+wMt3gZVb/mEjblw6Mg==" + }, + "runtime.any.System.Runtime.InteropServices": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "gmibdZ9x/eB6hf5le33DWLCQbhcIUD2vqoc0tBgqSUWlB8YjEzVJXyTPDO+ypKLlL90Kv3ZDrK7yPCNqcyhqCA==" + }, + "runtime.any.System.Text.Encoding": { + "type": "Transitive", + "resolved": "4.0.11", + "contentHash": "uweRMRDD4O8Iy8m4h1cJvoFIHNCzHMpipuxkRNAMML6EMzAhDCQTjgvRwki7PlUg8RGY1ctXnBZjT1rXvMZuRw==" + }, + "runtime.any.System.Threading.Tasks": { + "type": "Transitive", + "resolved": "4.0.11", + "contentHash": "CEvWO0IwtdCAsmCb9aAl59psy0hzx+whYh4DzbjNb0GsQmxw/G7bZEcrBtE8c9QupNVbu87c2xaMi6p4r1bpjA==" + }, + "runtime.win.System.Diagnostics.Debug": { + "type": "Transitive", + "resolved": "4.0.11", + "contentHash": "q8Fm954ezFLfmG0tHNUmsNy+qaEjWtWqYhWh3cGSVjtJwkcBsfigWCh+fdaIVZ9K7m+6lgb3ElL2BBU6G+RijA==" + }, + "runtime.win.System.Runtime.Extensions": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "U3F/M+djxVXuKJaoW2AGpAE2ZWAp372140jsX4d/ctqki+Qb61HuyQY4yUPSA/gdKGbbq6HXzZ6oxB6/G3MYPA==", + "dependencies": { + "System.Private.Uri": "4.0.1" + } + }, + "runtime.win7.System.Private.Uri": { + "type": "Transitive", + "resolved": "4.0.1", + "contentHash": "LPOuwNel9nJ+G751J/yb64zkodFzVUwYYukQ8vysjiHRBrnvsZOhIxvqKhG6od1szrBNkl8pw8VGvvcfQ/2VOA==" + }, + "System.Collections": { + "type": "Transitive", + "resolved": "4.0.11", + "contentHash": "YUJGz6eFKqS0V//mLt25vFGrrCvOnsXjlvFQs+KimpwNxug9x0Pzy4PlFMU3Q2IzqAa9G2L4LsK3+9vCBK7oTg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0", + "runtime.any.System.Collections": "4.0.11" + } + }, + "System.Diagnostics.Debug": { + "type": "Transitive", + "resolved": "4.0.11", + "contentHash": "w5U95fVKHY4G8ASs/K5iK3J5LY+/dLFd4vKejsnI/ZhBsWS9hQakfx3Zr7lRWKg4tAw9r4iktyvsTagWkqYCiw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0", + "runtime.win.System.Diagnostics.Debug": "4.0.11" + } + }, + "System.Diagnostics.Tracing": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "vDN1PoMZCkkdNjvZLql592oYJZgS7URcJzJ7bxeBgGtx5UtR5leNm49VmfHGqIffX4FKacHbI3H6UyNSHQknBg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0", + "runtime.any.System.Diagnostics.Tracing": "4.1.0" + } + }, + "System.Globalization": { + "type": "Transitive", + "resolved": "4.0.11", + "contentHash": "B95h0YLEL2oSnwF/XjqSWKnwKOy/01VWkNlsCeMTFJLLabflpGV26nK164eRs5GiaRSBGpOxQ3pKoSnnyZN5pg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0", + "runtime.any.System.Globalization": "4.0.11" + } + }, + "System.IO": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "3KlTJceQc3gnGIaHZ7UBZO26SHL1SHE4ddrmiwumFnId+CEHP+O8r386tZKaE6zlk5/mF8vifMBzHj9SaXN+mQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0", + "System.Text.Encoding": "4.0.11", + "System.Threading.Tasks": "4.0.11", + "runtime.any.System.IO": "4.1.0" + } + }, + "System.Private.Uri": { + "type": "Transitive", + "resolved": "4.0.1", + "contentHash": "OltceAn9yyNf9LZIqvf80DhdRH55iVu1fxowdR79018w1CWIRNojUZBStsiRHvADeKI5pXcM9EftOFikBQh5AA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "runtime.win7.System.Private.Uri": "4.0.1" + } + }, + "System.Reflection": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "JCKANJ0TI7kzoQzuwB/OoJANy1Lg338B6+JVacPl4TpUwi3cReg3nMLplMq2uqYfHFQpKIlHAUVAJlImZz/4ng==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.IO": "4.1.0", + "System.Reflection.Primitives": "4.0.1", + "System.Runtime": "4.1.0", + "runtime.any.System.Reflection": "4.1.0" + } + }, + "System.Reflection.Primitives": { + "type": "Transitive", + "resolved": "4.0.1", + "contentHash": "4inTox4wTBaDhB7V3mPvp9XlCbeGYWVEM9/fXALd52vNEAVisc1BoVWQPuUuD0Ga//dNbA/WeMy9u9mzLxGTHQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0", + "runtime.any.System.Reflection.Primitives": "4.0.1" + } + }, + "System.Resources.ResourceManager": { + "type": "Transitive", + "resolved": "4.0.1", + "contentHash": "TxwVeUNoTgUOdQ09gfTjvW411MF+w9MBYL7AtNVc+HtBCFlutPLhUCdZjNkjbhj3bNQWMdHboF0KIWEOjJssbA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Globalization": "4.0.11", + "System.Reflection": "4.1.0", + "System.Runtime": "4.1.0", + "runtime.any.System.Resources.ResourceManager": "4.0.1" + } + }, + "System.Runtime": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "v6c/4Yaa9uWsq+JMhnOFewrYkgdNHNG2eMKuNqRn8P733rNXeRCGvV5FkkjBXn2dbVkPXOsO0xjsEeM1q2zC0g==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "runtime.any.System.Runtime": "4.1.0" + } + }, + "System.Runtime.Extensions": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "CUOHjTT/vgP0qGW22U4/hDlOqXmcPq5YicBaXdUR2UiUoLwBT+olO6we4DVbq57jeX5uXH2uerVZhf0qGj+sVQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0", + "runtime.win.System.Runtime.Extensions": "4.1.0" + } + }, + "System.Runtime.Handles": { + "type": "Transitive", + "resolved": "4.0.1", + "contentHash": "nCJvEKguXEvk2ymk1gqj625vVnlK3/xdGzx0vOKicQkoquaTBJTP13AIYkocSUwHCLNBwUbXTqTWGDxBTWpt7g==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0", + "runtime.any.System.Runtime.Handles": "4.0.1" + } + }, + "System.Runtime.InteropServices": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "16eu3kjHS633yYdkjwShDHZLRNMKVi/s0bY8ODiqJ2RfMhDMAwxZaUaWVnZ2P71kr/or+X9o/xFWtNqz8ivieQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Reflection": "4.1.0", + "System.Reflection.Primitives": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Handles": "4.0.1", + "runtime.any.System.Runtime.InteropServices": "4.1.0" + } + }, "System.Security.AccessControl": { "type": "Transitive", "resolved": "4.7.0", @@ -244,6 +1169,28 @@ "type": "Transitive", "resolved": "4.7.0", "contentHash": "ojD0PX0XhneCsUbAZVKdb7h/70vyYMDYs85lwEI+LngEONe/17A0cFaRFqZU+sOEidcVswYWikYOQ9PPfjlbtQ==" + }, + "System.Text.Encoding": { + "type": "Transitive", + "resolved": "4.0.11", + "contentHash": "U3gGeMlDZXxCEiY4DwVLSacg+DFWCvoiX+JThA/rvw37Sqrku7sEFeVBBBMBnfB6FeZHsyDx85HlKL19x0HtZA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0", + "runtime.any.System.Text.Encoding": "4.0.11" + } + }, + "System.Threading.Tasks": { + "type": "Transitive", + "resolved": "4.0.11", + "contentHash": "k1S4Gc6IGwtHGT8188RSeGaX86Qw/wnrgNLshJvsdNUOPP9etMmo8S07c+UlOAx4K/xLuN9ivA1bD0LVurtIxQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0", + "runtime.any.System.Threading.Tasks": "4.0.11" + } } } } diff --git a/WalletWasabi.Tests/AcceptanceTests/HwiKatas.cs b/WalletWasabi.Tests/AcceptanceTests/HwiKatas.cs index a45913f2e5b..b685098f5aa 100644 --- a/WalletWasabi.Tests/AcceptanceTests/HwiKatas.cs +++ b/WalletWasabi.Tests/AcceptanceTests/HwiKatas.cs @@ -1,8 +1,6 @@ using NBitcoin; using System; -using System.Collections.Generic; using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; using WalletWasabi.Blockchain.Keys; @@ -24,7 +22,10 @@ public class HwiKatas #region SharedVariables // Bottleneck: user action on device. - public TimeSpan ReasonableRequestTimeout { get; } = TimeSpan.FromMinutes(3); + public TimeSpan ReasonableRequestTimeout { get; } = TimeSpan.FromMinutes(10); + + // Consolidating tx with 10 inputs and 1 output. + public PSBT Psbt => PSBT.Parse("cHNidP8BAP3DAQEAAAAKoAgcNIDwFrTyX86cP6lipJkCKCHfygR/5EKGSIEMrEUKAAAAAP////+gCBw0gPAWtPJfzpw/qWKkmQIoId/KBH/kQoZIgQysRQUAAAAA/////6AIHDSA8Ba08l/OnD+pYqSZAigh38oEf+RChkiBDKxFCQAAAAD/////V0tML9bPLpQzVjQlk3OLFPk3zHEi70veaxbWfYl943wAAAAAAP////97HMtaemhMIiEx+vFc4OkvWRZpYVg+EwP/n14aNsIkbwAAAAAA/////6AIHDSA8Ba08l/OnD+pYqSZAigh38oEf+RChkiBDKxFCAAAAAD/////oAgcNIDwFrTyX86cP6lipJkCKCHfygR/5EKGSIEMrEUEAAAAAP////+gCBw0gPAWtPJfzpw/qWKkmQIoId/KBH/kQoZIgQysRQcAAAAA/////04Kw6QaL2tGrE93QZ0HuLcalGmPtT4kRV+4Xr9lpGbQAAAAAAD/////oAgcNIDwFrTyX86cP6lipJkCKCHfygR/5EKGSIEMrEUGAAAAAP////8BvC0IAAAAAAAWABTl+rMcBqo1fLetPAELsqe0gK8a8gAAAAAAAQEfrNwDAAAAAAAWABSLlyzSus0cRTY9xYu/0Z1qbuSVBwEA/fUBAgAAAAABAdV3i93zngyRW1+6OzgKU1eBbDxbEF9qu8MsOrP/4lzvCgAAAAD+////C+gDAAAAAAAAFgAUBzo3ulcMS0XTqRgYUfycTgsHY9boAwAAAAAAABYAFB9MDddWWT6imiah4/TG9FUZX1ru6AMAAAAAAAAWABQk8f6LEYr12P9yzaqYfQqpA2ncBegDAAAAAAAAFgAUMAt78s8QcroNlkHuk5H+QRcCBhHoAwAAAAAAABYAFDPY5s1XGMCI6NJBZ+hBnPxcNIJN6AMAAAAAAAAWABRacCoi3vux/oFZwQVx5B44PaI3OegDAAAAAAAAFgAUYiHbFeQKUS3VYD10ckUtWZgOmWDoAwAAAAAAABYAFHuwA7dPwDAtVEuMzHrTjHVhYCeF6AMAAAAAAAAWABSBiRwCLrpyJyFQ4btxL8Crw1PziegDAAAAAAAAFgAUhmXws4i0bDZ3mRPaPpUEx2Ai2aCs3AMAAAAAABYAFIuXLNK6zRxFNj3Fi7/RnWpu5JUHAkcwRAIgDlmDxY+CuiplMV50p4gKYrrO5VBPRkWrnfyLy39PKZQCIDbpck4afD47Xw0Vqw1NtVbmqWIKqq0T4gjJHlLeTgvPASECkx9MH6NmZiwJKciMJM12+n5G9T/sbPSeZSf0EdtfWPSJBBwAIgYDcr8mR7pcGpxXmqhUvWTa+3PWszUwiOonfGlyASj2/isY5dvJy1QAAIAAAACAAAAAgAEAAAACAAAAAAEBH+gDAAAAAAAAFgAUWnAqIt77sf6BWcEFceQeOD2iNzkBAP31AQIAAAAAAQHVd4vd854MkVtfujs4ClNXgWw8WxBfarvDLDqz/+Jc7woAAAAA/v///wvoAwAAAAAAABYAFAc6N7pXDEtF06kYGFH8nE4LB2PW6AMAAAAAAAAWABQfTA3XVlk+opomoeP0xvRVGV9a7ugDAAAAAAAAFgAUJPH+ixGK9dj/cs2qmH0KqQNp3AXoAwAAAAAAABYAFDALe/LPEHK6DZZB7pOR/kEXAgYR6AMAAAAAAAAWABQz2ObNVxjAiOjSQWfoQZz8XDSCTegDAAAAAAAAFgAUWnAqIt77sf6BWcEFceQeOD2iNznoAwAAAAAAABYAFGIh2xXkClEt1WA9dHJFLVmYDplg6AMAAAAAAAAWABR7sAO3T8AwLVRLjMx604x1YWAnhegDAAAAAAAAFgAUgYkcAi66cichUOG7cS/Aq8NT84noAwAAAAAAABYAFIZl8LOItGw2d5kT2j6VBMdgItmgrNwDAAAAAAAWABSLlyzSus0cRTY9xYu/0Z1qbuSVBwJHMEQCIA5Zg8WPgroqZTFedKeICmK6zuVQT0ZFq538i8t/TymUAiA26XJOGnw+O18NFasNTbVW5qliCqqtE+IIyR5S3k4LzwEhApMfTB+jZmYsCSnIjCTNdvp+RvU/7Gz0nmUn9BHbX1j0iQQcACIGAlR8t3rrAiFIgKul+n8+VogH6YLXkfQmUm0bDZf209VkGOXbyctUAACAAAAAgAAAAIAAAAAAoAAAAAABAR/oAwAAAAAAABYAFIZl8LOItGw2d5kT2j6VBMdgItmgAQD99QECAAAAAAEB1XeL3fOeDJFbX7o7OApTV4FsPFsQX2q7wyw6s//iXO8KAAAAAP7///8L6AMAAAAAAAAWABQHOje6VwxLRdOpGBhR/JxOCwdj1ugDAAAAAAAAFgAUH0wN11ZZPqKaJqHj9Mb0VRlfWu7oAwAAAAAAABYAFCTx/osRivXY/3LNqph9CqkDadwF6AMAAAAAAAAWABQwC3vyzxByug2WQe6Tkf5BFwIGEegDAAAAAAAAFgAUM9jmzVcYwIjo0kFn6EGc/Fw0gk3oAwAAAAAAABYAFFpwKiLe+7H+gVnBBXHkHjg9ojc56AMAAAAAAAAWABRiIdsV5ApRLdVgPXRyRS1ZmA6ZYOgDAAAAAAAAFgAUe7ADt0/AMC1US4zMetOMdWFgJ4XoAwAAAAAAABYAFIGJHAIuunInIVDhu3EvwKvDU/OJ6AMAAAAAAAAWABSGZfCziLRsNneZE9o+lQTHYCLZoKzcAwAAAAAAFgAUi5cs0rrNHEU2PcWLv9Gdam7klQcCRzBEAiAOWYPFj4K6KmUxXnSniApius7lUE9GRaud/IvLf08plAIgNulyThp8PjtfDRWrDU21VuapYgqqrRPiCMkeUt5OC88BIQKTH0wfo2ZmLAkpyIwkzXb6fkb1P+xs9J5lJ/QR219Y9IkEHAAiBgKOC5f1h6OdqQShVZ67btysHEnYQnwz5r3wRX/f9vw0xxjl28nLVAAAgAAAAIAAAACAAAAAAKQAAAAAAQEfoIYBAAAAAAAWABSlcvbglTqY7BoSh6z8P7j1aoiNAwEA3gEAAAAAAQF7HMtaemhMIiEx+vFc4OkvWRZpYVg+EwP/n14aNsIkbwEAAAAA/v///wKghgEAAAAAABYAFKVy9uCVOpjsGhKHrPw/uPVqiI0Dmj8JAAAAAAAWABRbPNfznxRy80QaRMdGJv6Qt7czZgJHMEQCIHapAel2KIKL3ZUf/036V3tbqm7EqCX9sizzyF82ERcMAiBNwrGFLCGh8oEcmc5be8/cIcae0/ugA+9yClQSOHq3DQEhAzdMd377INd4UB4k3Af4EXwAYXcwF8mO5WT1vgqqBo0zzwccACIGArksToh0c7mdiHNG9y6kblvDP6moiZkB5g/C0eeBZdCtGOXbyctUAACAAAAAgAAAAIAAAAAAqAAAAAABAR+ghgEAAAAAABYAFLlPkZHZ7BDzG6fQ5eIVtPCUNVmYAQBxAQAAAAFOCsOkGi9rRqxPd0GdB7i3GpRpj7U+JEVfuF6/ZaRm0AEAAAAA/////wKghgEAAAAAABYAFLlPkZHZ7BDzG6fQ5eIVtPCUNVmYx8YKAAAAAAAWABTg+KiUglaOjpmo3u2Vj3WCQnz0SgAAAAAiBgL+Vecdyprxr4AIlp1kzajgBM9nEOM9x9Db2LQeD9MrIhjl28nLVAAAgAAAAIAAAACAAAAAAKcAAAAAAQEf6AMAAAAAAAAWABSBiRwCLrpyJyFQ4btxL8Crw1PziQEA/fUBAgAAAAABAdV3i93zngyRW1+6OzgKU1eBbDxbEF9qu8MsOrP/4lzvCgAAAAD+////C+gDAAAAAAAAFgAUBzo3ulcMS0XTqRgYUfycTgsHY9boAwAAAAAAABYAFB9MDddWWT6imiah4/TG9FUZX1ru6AMAAAAAAAAWABQk8f6LEYr12P9yzaqYfQqpA2ncBegDAAAAAAAAFgAUMAt78s8QcroNlkHuk5H+QRcCBhHoAwAAAAAAABYAFDPY5s1XGMCI6NJBZ+hBnPxcNIJN6AMAAAAAAAAWABRacCoi3vux/oFZwQVx5B44PaI3OegDAAAAAAAAFgAUYiHbFeQKUS3VYD10ckUtWZgOmWDoAwAAAAAAABYAFHuwA7dPwDAtVEuMzHrTjHVhYCeF6AMAAAAAAAAWABSBiRwCLrpyJyFQ4btxL8Crw1PziegDAAAAAAAAFgAUhmXws4i0bDZ3mRPaPpUEx2Ai2aCs3AMAAAAAABYAFIuXLNK6zRxFNj3Fi7/RnWpu5JUHAkcwRAIgDlmDxY+CuiplMV50p4gKYrrO5VBPRkWrnfyLy39PKZQCIDbpck4afD47Xw0Vqw1NtVbmqWIKqq0T4gjJHlLeTgvPASECkx9MH6NmZiwJKciMJM12+n5G9T/sbPSeZSf0EdtfWPSJBBwAIgYC2CWPTrWGLG21ySpEyhzUSM3XG0fH2A2tIODkw0zj2WQY5dvJy1QAAIAAAACAAAAAgAAAAAChAAAAAAEBH+gDAAAAAAAAFgAUM9jmzVcYwIjo0kFn6EGc/Fw0gk0BAP31AQIAAAAAAQHVd4vd854MkVtfujs4ClNXgWw8WxBfarvDLDqz/+Jc7woAAAAA/v///wvoAwAAAAAAABYAFAc6N7pXDEtF06kYGFH8nE4LB2PW6AMAAAAAAAAWABQfTA3XVlk+opomoeP0xvRVGV9a7ugDAAAAAAAAFgAUJPH+ixGK9dj/cs2qmH0KqQNp3AXoAwAAAAAAABYAFDALe/LPEHK6DZZB7pOR/kEXAgYR6AMAAAAAAAAWABQz2ObNVxjAiOjSQWfoQZz8XDSCTegDAAAAAAAAFgAUWnAqIt77sf6BWcEFceQeOD2iNznoAwAAAAAAABYAFGIh2xXkClEt1WA9dHJFLVmYDplg6AMAAAAAAAAWABR7sAO3T8AwLVRLjMx604x1YWAnhegDAAAAAAAAFgAUgYkcAi66cichUOG7cS/Aq8NT84noAwAAAAAAABYAFIZl8LOItGw2d5kT2j6VBMdgItmgrNwDAAAAAAAWABSLlyzSus0cRTY9xYu/0Z1qbuSVBwJHMEQCIA5Zg8WPgroqZTFedKeICmK6zuVQT0ZFq538i8t/TymUAiA26XJOGnw+O18NFasNTbVW5qliCqqtE+IIyR5S3k4LzwEhApMfTB+jZmYsCSnIjCTNdvp+RvU/7Gz0nmUn9BHbX1j0iQQcACIGAlSSvi0SFD2zSsTwCu/tj9bHkBFeryQ7wnJOmOh8ozNbGOXbyctUAACAAAAAgAAAAIAAAAAAnwAAAAABAR/oAwAAAAAAABYAFHuwA7dPwDAtVEuMzHrTjHVhYCeFAQD99QECAAAAAAEB1XeL3fOeDJFbX7o7OApTV4FsPFsQX2q7wyw6s//iXO8KAAAAAP7///8L6AMAAAAAAAAWABQHOje6VwxLRdOpGBhR/JxOCwdj1ugDAAAAAAAAFgAUH0wN11ZZPqKaJqHj9Mb0VRlfWu7oAwAAAAAAABYAFCTx/osRivXY/3LNqph9CqkDadwF6AMAAAAAAAAWABQwC3vyzxByug2WQe6Tkf5BFwIGEegDAAAAAAAAFgAUM9jmzVcYwIjo0kFn6EGc/Fw0gk3oAwAAAAAAABYAFFpwKiLe+7H+gVnBBXHkHjg9ojc56AMAAAAAAAAWABRiIdsV5ApRLdVgPXRyRS1ZmA6ZYOgDAAAAAAAAFgAUe7ADt0/AMC1US4zMetOMdWFgJ4XoAwAAAAAAABYAFIGJHAIuunInIVDhu3EvwKvDU/OJ6AMAAAAAAAAWABSGZfCziLRsNneZE9o+lQTHYCLZoKzcAwAAAAAAFgAUi5cs0rrNHEU2PcWLv9Gdam7klQcCRzBEAiAOWYPFj4K6KmUxXnSniApius7lUE9GRaud/IvLf08plAIgNulyThp8PjtfDRWrDU21VuapYgqqrRPiCMkeUt5OC88BIQKTH0wfo2ZmLAkpyIwkzXb6fkb1P+xs9J5lJ/QR219Y9IkEHAAiBgIf2TNT7AHInw8Vsmu+DZUoZmDd8mvCFFCMFINtpzUi9xjl28nLVAAAgAAAAIAAAACAAAAAAKUAAAAAAQEfoIYBAAAAAAAWABSTk6xQkvf7KJlWXjAPxGICUoJj+wEAcQEAAAABFUw594dqnOBVzpedvjFiiQCEW7ibSjbCZz3Xo93pzKgAAAAAAP////8CoIYBAAAAAAAWABSTk6xQkvf7KJlWXjAPxGICUoJj+/RNDAAAAAAAFgAUMsFekYmPZudpx+qOLpV2/c1H6N4AAAAAIgYDQQPvgooxebegqVpONENCoFVGGDRaC8ZlHf9rFhLYotEY5dvJy1QAAIAAAACAAAAAgAAAAACmAAAAAAEBH+gDAAAAAAAAFgAUYiHbFeQKUS3VYD10ckUtWZgOmWABAP31AQIAAAAAAQHVd4vd854MkVtfujs4ClNXgWw8WxBfarvDLDqz/+Jc7woAAAAA/v///wvoAwAAAAAAABYAFAc6N7pXDEtF06kYGFH8nE4LB2PW6AMAAAAAAAAWABQfTA3XVlk+opomoeP0xvRVGV9a7ugDAAAAAAAAFgAUJPH+ixGK9dj/cs2qmH0KqQNp3AXoAwAAAAAAABYAFDALe/LPEHK6DZZB7pOR/kEXAgYR6AMAAAAAAAAWABQz2ObNVxjAiOjSQWfoQZz8XDSCTegDAAAAAAAAFgAUWnAqIt77sf6BWcEFceQeOD2iNznoAwAAAAAAABYAFGIh2xXkClEt1WA9dHJFLVmYDplg6AMAAAAAAAAWABR7sAO3T8AwLVRLjMx604x1YWAnhegDAAAAAAAAFgAUgYkcAi66cichUOG7cS/Aq8NT84noAwAAAAAAABYAFIZl8LOItGw2d5kT2j6VBMdgItmgrNwDAAAAAAAWABSLlyzSus0cRTY9xYu/0Z1qbuSVBwJHMEQCIA5Zg8WPgroqZTFedKeICmK6zuVQT0ZFq538i8t/TymUAiA26XJOGnw+O18NFasNTbVW5qliCqqtE+IIyR5S3k4LzwEhApMfTB+jZmYsCSnIjCTNdvp+RvU/7Gz0nmUn9BHbX1j0iQQcACIGA5wMYXFIdfB0WoyPnwvds3QbMM9aICQuR70bNEayGJRBGOXbyctUAACAAAAAgAAAAIAAAAAAowAAAAAiAgNUXxoPAaJJR0fVyITnbB80AarA7xtN3c4xP5jgwZM+PRjl28nLVAAAgAAAAIAAAACAAAAAAKkAAAAA", Network.TestNet); #endregion SharedVariables @@ -33,12 +34,14 @@ public async Task TrezorTKataAsync() { // --- USER INTERACTIONS --- // - // Connect an already initialized device and unlock it. + // Connect and initialize your Trezor T with the following seed phrase: + // more maid moon upgrade layer alter marine screen benefit way cover alcohol // Run this test. - // displayaddress request: refuse - // displayaddress request: confirm - // displayaddress request: confirm - // signtx request: confirm + // displayaddress request: refuse 1 time + // displayaddress request: confirm 2 times + // displayaddress request: confirm 1 time + // signtx request: refuse 1 time + // signtx request: Hold to confirm // // --- USER INTERACTIONS --- @@ -50,7 +53,7 @@ public async Task TrezorTKataAsync() HwiEnumerateEntry entry = enumerate.Single(); Assert.NotNull(entry.Path); Assert.Equal(HardwareWalletModels.Trezor_T, entry.Model); - Assert.True(entry.Fingerprint.HasValue); + Assert.NotNull(entry.Fingerprint); string devicePath = entry.Path; HardwareWalletModels deviceType = entry.Model; @@ -88,12 +91,15 @@ public async Task TrezorTKataAsync() Assert.Equal(expectedAddress1, address1); Assert.Equal(expectedAddress2, address2); - // USER: CONFIRM - PSBT psbt = BuildPsbt(network, fingerprint, xpub1, keyPath1); - PSBT signedPsbt = await client.SignTxAsync(deviceType, devicePath, psbt, cts.Token); + // USER SHOULD REFUSE ACTION + var result = await Assert.ThrowsAsync(async () => await client.SignTxAsync(deviceType, devicePath, Psbt, cts.Token)); + Assert.Equal(HwiErrorCode.ActionCanceled, result.ErrorCode); + + // USER: Hold to confirm + PSBT signedPsbt = await client.SignTxAsync(deviceType, devicePath, Psbt, cts.Token); Transaction signedTx = signedPsbt.GetOriginalTransaction(); - Assert.Equal(psbt.GetOriginalTransaction().GetHash(), signedTx.GetHash()); + Assert.Equal(Psbt.GetOriginalTransaction().GetHash(), signedTx.GetHash()); var checkResult = signedTx.Check(); Assert.Equal(TransactionCheckResult.Success, checkResult); @@ -129,41 +135,13 @@ public async Task TrezorOneKataAsync() await Assert.ThrowsAsync(async () => await client.RestoreAsync(deviceType, devicePath, false, cts.Token)); } - private static PSBT BuildPsbt(Network network, HDFingerprint fingerprint, ExtPubKey xpub, KeyPath xpubKeyPath) - { - var deriveSendFromKeyPath = new KeyPath("1/0"); - var deriveSendToKeyPath = new KeyPath("0/0"); - - KeyPath sendFromKeyPath = xpubKeyPath.Derive(deriveSendFromKeyPath); - KeyPath sendToKeyPath = xpubKeyPath.Derive(deriveSendToKeyPath); - - PubKey sendFromPubKey = xpub.Derive(deriveSendFromKeyPath).PubKey; - PubKey sendToPubKey = xpub.Derive(deriveSendToKeyPath).PubKey; - - BitcoinAddress sendFromAddress = sendFromPubKey.GetAddress(ScriptPubKeyType.Segwit, network); - BitcoinAddress sendToAddress = sendToPubKey.GetAddress(ScriptPubKeyType.Segwit, network); - - TransactionBuilder builder = network.CreateTransactionBuilder(); - builder = builder.AddCoins(new Coin(uint256.One, 0, Money.Coins(1), sendFromAddress.ScriptPubKey)); - builder.Send(sendToAddress.ScriptPubKey, Money.Coins(0.99999m)); - PSBT psbt = builder - .SendFees(Money.Coins(0.00001m)) - .BuildPSBT(false); - - var rootKeyPath1 = new RootedKeyPath(fingerprint, sendFromKeyPath); - var rootKeyPath2 = new RootedKeyPath(fingerprint, sendToKeyPath); - - psbt.AddKeyPath(sendFromPubKey, rootKeyPath1, sendFromAddress.ScriptPubKey); - psbt.AddKeyPath(sendToPubKey, rootKeyPath2, sendToAddress.ScriptPubKey); - return psbt; - } - [Fact] public async Task ColdCardKataAsync() { // --- USER INTERACTIONS --- // - // Connect an already initialized device and unlock it. + // Connect and initialize your Coldcard with the following seed phrase: + // more maid moon upgrade layer alter marine screen benefit way cover alcohol // Run this test. // signtx request: refuse // signtx request: confirm @@ -178,7 +156,7 @@ public async Task ColdCardKataAsync() HwiEnumerateEntry entry = enumerate.Single(); Assert.NotNull(entry.Path); Assert.Equal(HardwareWalletModels.Coldcard, entry.Model); - Assert.True(entry.Fingerprint.HasValue); + Assert.NotNull(entry.Fingerprint); string devicePath = entry.Path; HardwareWalletModels deviceType = entry.Model; @@ -206,17 +184,15 @@ public async Task ColdCardKataAsync() Assert.NotNull(xpub2); Assert.NotEqual(xpub1, xpub2); - PSBT psbt = BuildPsbt(network, fingerprint, xpub1, keyPath1); - // USER: REFUSE - var ex = await Assert.ThrowsAsync(async () => await client.SignTxAsync(deviceType, devicePath, psbt, cts.Token)); + var ex = await Assert.ThrowsAsync(async () => await client.SignTxAsync(deviceType, devicePath, Psbt, cts.Token)); Assert.Equal(HwiErrorCode.ActionCanceled, ex.ErrorCode); // USER: CONFIRM - PSBT signedPsbt = await client.SignTxAsync(deviceType, devicePath, psbt, cts.Token); + PSBT signedPsbt = await client.SignTxAsync(deviceType, devicePath, Psbt, cts.Token); Transaction signedTx = signedPsbt.GetOriginalTransaction(); - Assert.Equal(psbt.GetOriginalTransaction().GetHash(), signedTx.GetHash()); + Assert.Equal(Psbt.GetOriginalTransaction().GetHash(), signedTx.GetHash()); var checkResult = signedTx.Check(); Assert.Equal(TransactionCheckResult.Success, checkResult); @@ -239,13 +215,18 @@ public async Task LedgerNanoSKataAsync() { // --- USER INTERACTIONS --- // - // Connect an already initialized device and unlock it and enter to Bitcoin App. + // Connect and initialize your Nano S with the following seed phrase: + // more maid moon upgrade layer alter marine screen benefit way cover alcohol // Run this test. - // displayaddress request: refuse (accept Warning messages) - // displayaddress request: confirm - // displayaddress request: confirm - // signtx request: refuse - // signtx request: confirm + // displayaddress request(derivation path): approve + // displayaddress request: reject + // displayaddress request(derivation path): approve + // displayaddress request: approve + // displayaddress request(derivation path): approve + // displayaddress request: approve + // signtx request: reject + // signtx request: accept + // confirm transaction: accept and send // // --- USER INTERACTIONS --- @@ -256,7 +237,90 @@ public async Task LedgerNanoSKataAsync() HwiEnumerateEntry entry = Assert.Single(enumerate); Assert.NotNull(entry.Path); Assert.Equal(HardwareWalletModels.Ledger_Nano_S, entry.Model); - Assert.True(entry.Fingerprint.HasValue); + Assert.NotNull(entry.Fingerprint); + Assert.Null(entry.Code); + Assert.Null(entry.Error); + Assert.Null(entry.SerialNumber); + Assert.False(entry.NeedsPassphraseSent); + Assert.False(entry.NeedsPinSent); + + string devicePath = entry.Path; + HardwareWalletModels deviceType = entry.Model; + HDFingerprint fingerprint = entry.Fingerprint.Value; + + await Assert.ThrowsAsync(async () => await client.SetupAsync(deviceType, devicePath, false, cts.Token)); + + await Assert.ThrowsAsync(async () => await client.RestoreAsync(deviceType, devicePath, false, cts.Token)); + + await Assert.ThrowsAsync(async () => await client.PromptPinAsync(deviceType, devicePath, cts.Token)); + + await Assert.ThrowsAsync(async () => await client.SendPinAsync(deviceType, devicePath, 1111, cts.Token)); + + KeyPath keyPath1 = KeyManager.DefaultAccountKeyPath; + KeyPath keyPath2 = KeyManager.DefaultAccountKeyPath.Derive(1); + ExtPubKey xpub1 = await client.GetXpubAsync(deviceType, devicePath, keyPath1, cts.Token); + ExtPubKey xpub2 = await client.GetXpubAsync(deviceType, devicePath, keyPath2, cts.Token); + Assert.NotNull(xpub1); + Assert.NotNull(xpub2); + Assert.NotEqual(xpub1, xpub2); + + // USER SHOULD REFUSE ACTION + await Assert.ThrowsAsync(async () => await client.DisplayAddressAsync(deviceType, devicePath, keyPath1, cts.Token)); + + // USER: CONFIRM + BitcoinWitPubKeyAddress address1 = await client.DisplayAddressAsync(deviceType, devicePath, keyPath1, cts.Token); + // USER: CONFIRM + BitcoinWitPubKeyAddress address2 = await client.DisplayAddressAsync(fingerprint, keyPath2, cts.Token); + Assert.NotNull(address1); + Assert.NotNull(address2); + Assert.NotEqual(address1, address2); + var expectedAddress1 = xpub1.PubKey.GetAddress(ScriptPubKeyType.Segwit, network); + var expectedAddress2 = xpub2.PubKey.GetAddress(ScriptPubKeyType.Segwit, network); + Assert.Equal(expectedAddress1, address1); + Assert.Equal(expectedAddress2, address2); + + // USER: REFUSE + var ex = await Assert.ThrowsAsync(async () => await client.SignTxAsync(deviceType, devicePath, Psbt, cts.Token)); + Assert.Equal(HwiErrorCode.BadArgument, ex.ErrorCode); + + // USER: CONFIRM + PSBT signedPsbt = await client.SignTxAsync(deviceType, devicePath, Psbt, cts.Token); + + Transaction signedTx = signedPsbt.GetOriginalTransaction(); + Assert.Equal(Psbt.GetOriginalTransaction().GetHash(), signedTx.GetHash()); + + var checkResult = signedTx.Check(); + Assert.Equal(TransactionCheckResult.Success, checkResult); + } + + [Fact] + public async Task LedgerNanoXKataAsync() + { + // --- USER INTERACTIONS --- + // + // Connect and initialize your Nano X with the following seed phrase: + // more maid moon upgrade layer alter marine screen benefit way cover alcohol + // Run this test. + // displayaddress request(derivation path): approve + // displayaddress request: reject + // displayaddress request(derivation path): approve + // displayaddress request: approve + // displayaddress request(derivation path): approve + // displayaddress request: approve + // signtx request: reject + // signtx request: accept + // confirm transaction: accept and send + // + // --- USER INTERACTIONS --- + + var network = Network.Main; + var client = new HwiClient(network); + using var cts = new CancellationTokenSource(ReasonableRequestTimeout); + var enumerate = await client.EnumerateAsync(cts.Token); + HwiEnumerateEntry entry = Assert.Single(enumerate); + Assert.NotNull(entry.Path); + Assert.Equal(HardwareWalletModels.Ledger_Nano_X, entry.Model); + Assert.NotNull(entry.Fingerprint); Assert.Null(entry.Code); Assert.Null(entry.Error); Assert.Null(entry.SerialNumber); @@ -299,15 +363,14 @@ public async Task LedgerNanoSKataAsync() Assert.Equal(expectedAddress2, address2); // USER: REFUSE - PSBT psbt = BuildPsbt(network, fingerprint, xpub1, keyPath1); - var ex = await Assert.ThrowsAsync(async () => await client.SignTxAsync(deviceType, devicePath, psbt, cts.Token)); + var ex = await Assert.ThrowsAsync(async () => await client.SignTxAsync(deviceType, devicePath, Psbt, cts.Token)); Assert.Equal(HwiErrorCode.BadArgument, ex.ErrorCode); // USER: CONFIRM - PSBT signedPsbt = await client.SignTxAsync(deviceType, devicePath, psbt, cts.Token); + PSBT signedPsbt = await client.SignTxAsync(deviceType, devicePath, Psbt, cts.Token); Transaction signedTx = signedPsbt.GetOriginalTransaction(); - Assert.Equal(psbt.GetOriginalTransaction().GetHash(), signedTx.GetHash()); + Assert.Equal(Psbt.GetOriginalTransaction().GetHash(), signedTx.GetHash()); var checkResult = signedTx.Check(); Assert.Equal(TransactionCheckResult.Success, checkResult); diff --git a/WalletWasabi.Tests/IntegrationTests/P2pTests.cs b/WalletWasabi.Tests/IntegrationTests/P2pTests.cs index 7c458505a7b..ad46e4fc619 100644 --- a/WalletWasabi.Tests/IntegrationTests/P2pTests.cs +++ b/WalletWasabi.Tests/IntegrationTests/P2pTests.cs @@ -57,14 +57,16 @@ public async Task TestServicesAsync(string networkString) } var dataDir = Path.Combine(Global.Instance.DataDir, EnvironmentHelpers.GetCallerFileName()); - BitcoinStore bitcoinStore = new BitcoinStore(Path.Combine(dataDir, EnvironmentHelpers.GetMethodName()), network, - new IndexStore(network, new SmartHeaderChain()), new AllTransactionStore(), new MempoolService()); - + var dir = Path.Combine(dataDir, EnvironmentHelpers.GetMethodName()); + var indexStore = new IndexStore(Path.Combine(dir, "indexStore"), network, new SmartHeaderChain()); + var transactionStore = new AllTransactionStore(Path.Combine(dir, "transactionStore"), network); + var mempoolService = new MempoolService(); + var blocks = new FileSystemBlockRepository(Path.Combine(dir, "blocks"), network); + BitcoinStore bitcoinStore = new BitcoinStore(indexStore, transactionStore, mempoolService, blocks); await bitcoinStore.InitializeAsync(); 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 @@ -101,7 +103,7 @@ public async Task TestServicesAsync(string networkString) ServiceConfiguration serviceConfig = new ServiceConfiguration(MixUntilAnonymitySet.PrivacyLevelStrong.ToString(), 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)); + bitcoinStore.BlockRepository); using Wallet wallet = Wallet.CreateAndRegisterServices( network, @@ -113,7 +115,7 @@ public async Task TestServicesAsync(string networkString) new ServiceConfiguration(MixUntilAnonymitySet.PrivacyLevelStrong.ToString(), 2, 21, 50, new IPEndPoint(IPAddress.Loopback, network.DefaultPort), Money.Coins(Constants.DefaultDustThreshold)), syncer, blockProvider); - Assert.True(Directory.Exists(blocksFolderPath)); + Assert.True(Directory.Exists(blocks.BlocksFolderPath)); try { @@ -142,7 +144,7 @@ public async Task TestServicesAsync(string networkString) var hashArray = blocksToDownload.ToArray(); foreach (var block in await Task.WhenAll(downloadTasks)) { - Assert.True(File.Exists(Path.Combine(blocksFolderPath, hashArray[i].ToString()))); + Assert.True(File.Exists(Path.Combine(blocks.BlocksFolderPath, hashArray[i].ToString()))); i++; } @@ -160,9 +162,9 @@ public async Task TestServicesAsync(string networkString) await wallet.StopAsync(CancellationToken.None); } - if (Directory.Exists(blocksFolderPath)) + if (Directory.Exists(blocks.BlocksFolderPath)) { - Directory.Delete(blocksFolderPath, recursive: true); + Directory.Delete(blocks.BlocksFolderPath, recursive: true); } IoHelpers.EnsureContainingDirectoryExists(addressManagerFilePath); diff --git a/WalletWasabi.Tests/RegressionTests/BuildTests.cs b/WalletWasabi.Tests/RegressionTests/BuildTests.cs index 8b73107b657..a856861fb1f 100644 --- a/WalletWasabi.Tests/RegressionTests/BuildTests.cs +++ b/WalletWasabi.Tests/RegressionTests/BuildTests.cs @@ -62,10 +62,9 @@ public async Task BuildTransactionValidationsTestAsync() // 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)); + bitcoinStore.BlockRepository); using var wallet = Wallet.CreateAndRegisterServices(network, bitcoinStore, keyManager, synchronizer, nodes, workDir, serviceConfiguration, synchronizer, blockProvider); wallet.NewFilterProcessed += Common.Wallet_NewFilterProcessed; @@ -226,10 +225,9 @@ public async Task BuildTransactionReorgsTestAsync() // 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)); + bitcoinStore.BlockRepository); var walletManager = new WalletManager(network, new WalletDirectories(workDir)); walletManager.RegisterServices(bitcoinStore, synchronizer, nodes, serviceConfiguration, synchronizer, blockProvider); diff --git a/WalletWasabi.Tests/RegressionTests/CoinJoinTests.cs b/WalletWasabi.Tests/RegressionTests/CoinJoinTests.cs index ff1a074bf42..387031dbecc 100644 --- a/WalletWasabi.Tests/RegressionTests/CoinJoinTests.cs +++ b/WalletWasabi.Tests/RegressionTests/CoinJoinTests.cs @@ -1326,15 +1326,13 @@ public async Task CoinJoinMultipleRoundTestsAsync() // 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)); + bitcoinStore.BlockRepository); CachedBlockProvider blockProvider2 = new CachedBlockProvider( new P2pBlockProvider(nodes2, null, synchronizer, serviceConfiguration, network), - new FileSystemBlockRepository(blocksFolderPath, network)); + bitcoinStore.BlockRepository); using var wallet = Wallet.CreateAndRegisterServices(network, bitcoinStore, keyManager, synchronizer, nodes, workDir, serviceConfiguration, synchronizer, blockProvider); wallet.NewFilterProcessed += Common.Wallet_NewFilterProcessed; diff --git a/WalletWasabi.Tests/RegressionTests/Common.cs b/WalletWasabi.Tests/RegressionTests/Common.cs index 1abbe7dc930..258422721d9 100644 --- a/WalletWasabi.Tests/RegressionTests/Common.cs +++ b/WalletWasabi.Tests/RegressionTests/Common.cs @@ -93,7 +93,11 @@ private static async Task AssertFiltersInitializedAsync(RegTestFixture regTestFi var serviceConfiguration = new ServiceConfiguration(MixUntilAnonymitySet.PrivacyLevelSome.ToString(), 2, 21, 50, regTestFixture.BackendRegTestNode.P2pEndPoint, Money.Coins(Constants.DefaultDustThreshold)); var dir = GetWorkDir(callerFilePath, callerMemberName); - var bitcoinStore = new BitcoinStore(dir, network, new IndexStore(network, new SmartHeaderChain()), new AllTransactionStore(), new MempoolService()); + var indexStore = new IndexStore(Path.Combine(dir, "indexStore"), network, new SmartHeaderChain()); + var transactionStore = new AllTransactionStore(Path.Combine(dir, "transactionStore"), network); + var mempoolService = new MempoolService(); + var blocks = new FileSystemBlockRepository(Path.Combine(dir, "blocks"), network); + var bitcoinStore = new BitcoinStore(indexStore, transactionStore, mempoolService, blocks); await bitcoinStore.InitializeAsync(); return ("password", global.RpcClient, network, global.Coordinator, serviceConfiguration, bitcoinStore, global); } diff --git a/WalletWasabi.Tests/RegressionTests/SendTests.cs b/WalletWasabi.Tests/RegressionTests/SendTests.cs index af2000e62f8..1aee8b762ca 100644 --- a/WalletWasabi.Tests/RegressionTests/SendTests.cs +++ b/WalletWasabi.Tests/RegressionTests/SendTests.cs @@ -60,11 +60,10 @@ public async Task SendTestsAsync() // 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)); + bitcoinStore.BlockRepository); var walletManager = new WalletManager(network, new WalletDirectories(workDir)); walletManager.RegisterServices(bitcoinStore, synchronizer, nodes, serviceConfiguration, synchronizer, blockProvider); @@ -540,11 +539,10 @@ public async Task SpendUnconfirmedTxTestAsync() // 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)); + bitcoinStore.BlockRepository); var walletManager = new WalletManager(network, new WalletDirectories(workDir)); walletManager.RegisterServices(bitcoinStore, synchronizer, nodes, serviceConfiguration, synchronizer, blockProvider); @@ -714,11 +712,10 @@ public async Task ReplaceByFeeTxTestAsync() // 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)); + bitcoinStore.BlockRepository); using var wallet = Wallet.CreateAndRegisterServices(network, bitcoinStore, keyManager, synchronizer, nodes, workDir, serviceConfiguration, synchronizer, blockProvider); wallet.NewFilterProcessed += Common.Wallet_NewFilterProcessed; diff --git a/WalletWasabi.Tests/RegressionTests/WalletTests.cs b/WalletWasabi.Tests/RegressionTests/WalletTests.cs index 52aff28af83..cd5d8324998 100644 --- a/WalletWasabi.Tests/RegressionTests/WalletTests.cs +++ b/WalletWasabi.Tests/RegressionTests/WalletTests.cs @@ -280,11 +280,10 @@ public async Task WalletTestsAsync() // 4. 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)); + bitcoinStore.BlockRepository); using var wallet = Wallet.CreateAndRegisterServices(network, bitcoinStore, keyManager, synchronizer, nodes, workDir, serviceConfiguration, synchronizer, blockProvider); wallet.NewFilterProcessed += Common.Wallet_NewFilterProcessed; diff --git a/WalletWasabi.Tests/UnitTests/BestEffortEndpointConnectorTests.cs b/WalletWasabi.Tests/UnitTests/BestEffortEndpointConnectorTests.cs new file mode 100644 index 00000000000..e8260a803ea --- /dev/null +++ b/WalletWasabi.Tests/UnitTests/BestEffortEndpointConnectorTests.cs @@ -0,0 +1,78 @@ +using System; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; +using NBitcoin; +using NBitcoin.Protocol; +using NBitcoin.Protocol.Behaviors; +using NBitcoin.Protocol.Connectors; +using Xunit; + +namespace WalletWasabi.Tests.UnitTests +{ + public class BestEffortEndpointConnectorTests + { + [Fact] + public async Task CanConnectWithDifferentModesAsync() + { + var connector = new BestEffortEndpointConnector(6); + var nodeConnectionParameters = new NodeConnectionParameters(); + nodeConnectionParameters.TemplateBehaviors.Add(new SocksSettingsBehavior(new IPEndPoint(IPAddress.Loopback, 8090), onlyForOnionHosts: true, networkCredential: null, streamIsolation: true)); + + using var nodes = new NodesGroup(Network.TestNet, nodeConnectionParameters); + + async Task ConnectAsync(EndPoint endpoint) + { + using var socket = new Socket(SocketType.Stream, ProtocolType.Tcp); + await connector.ConnectSocket(socket, endpoint, nodeConnectionParameters, CancellationToken.None); + } + + Exception ex; + + // Try to connect to a non-onion address. + ex = await Assert.ThrowsAnyAsync( + async () => await ConnectAsync(new IPEndPoint(IPAddress.Loopback, 180))); + Assert.Contains("refused", ex.Message); + Assert.False(connector.State.AllowOnlyTorEndpoints); + + // Try to connect to an onion address (it has to fail because there is no real socks proxy listening). + ex = await Assert.ThrowsAnyAsync( + async () => await ConnectAsync(new DnsEndPoint("nec4kn4ghql7p7an.onion", 180))); + Assert.Contains("refused", ex.Message); + Assert.False(connector.State.AllowOnlyTorEndpoints); + + // Simulate we lost connection. + connector.State.ConnectedNodesCount = 10; + ex = await Assert.ThrowsAnyAsync( + async () => await ConnectAsync(new IPEndPoint(IPAddress.Loopback, 180))); + Assert.Contains("refused", ex.Message); + Assert.True(connector.State.AllowOnlyTorEndpoints); + + ex = await Assert.ThrowsAnyAsync( + async () => await ConnectAsync(new DnsEndPoint("nec4kn4ghql7p7an.onion", 180))); + Assert.Contains("refused", ex.Message); + Assert.True(connector.State.AllowOnlyTorEndpoints); + + // Simulate we lost connection. + connector.State.ConnectedNodesCount = 0; + ex = await Assert.ThrowsAnyAsync( + async () => await ConnectAsync(new IPEndPoint(IPAddress.Loopback, 180))); + Assert.Contains("refused", ex.Message); + Assert.False(connector.State.AllowOnlyTorEndpoints); + + // Try to connect to an onion address (it has to fail because there is no real socks proxy listening). + ex = await Assert.ThrowsAnyAsync( + async () => await ConnectAsync(new DnsEndPoint("nec4kn4ghql7p7an.onion", 180))); + Assert.Contains("refused", ex.Message); + Assert.False(connector.State.AllowOnlyTorEndpoints); + + // Enough peers with recent connection. + connector.State.ConnectedNodesCount = 10; + ex = await Assert.ThrowsAnyAsync( + async () => await ConnectAsync(new DnsEndPoint("nec4kn4ghql7p7an.onion", 180))); + Assert.Contains("refused", ex.Message); + Assert.True(connector.State.AllowOnlyTorEndpoints); + } + } +} diff --git a/WalletWasabi.Tests/UnitTests/BitcoinCore/BitcoindBinaryHashesTests.cs b/WalletWasabi.Tests/UnitTests/BitcoinCore/BitcoindBinaryHashesTests.cs new file mode 100644 index 00000000000..54cc2bb3833 --- /dev/null +++ b/WalletWasabi.Tests/UnitTests/BitcoinCore/BitcoindBinaryHashesTests.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Threading; +using WalletWasabi.Microservices; +using Xunit; + +namespace WalletWasabi.Tests.UnitTests.BitcoinCore +{ + public class BitcoindBinaryHashesTests + { + /// + /// Bitcoin Knots distributes only SHA256 checksums for installers and for zip archives and not their content, so the validity of the hashes below depends on peer review consensus. + /// + /// To verify a file hash, you can use, for example, certUtil -hashfile bitcoind.exe SHA256 command. + [Fact] + public void VerifyBitcoindBinaryChecksumHashes() + { + using var cts = new CancellationTokenSource(5_000); + + Dictionary expectedHashes = new Dictionary() + { + { OSPlatform.Windows, "957d43d6290ad1bda3e4ee6dc1303a7dac56c378ad4a4b4896610fb02f4fe177" }, + { OSPlatform.Linux, "72c713789869010a70f264a03261e7fcb9e2dc3052ba618f2e5056fb45cdc6e5" }, + { OSPlatform.OSX, "182cd983fba123b77d6deaefee4669b9e256dda8274c86249e6f4b852dd02924" }, + }; + + foreach (var item in expectedHashes) + { + string binaryFolder = MicroserviceHelpers.GetBinaryFolder(item.Key); + string filePath = Path.Combine(binaryFolder, item.Key == OSPlatform.Windows ? "bitcoind.exe" : "bitcoind"); + + using SHA256 sha256 = SHA256.Create(); + using FileStream fileStream = File.OpenRead(filePath); + Assert.Equal(item.Value, ByteHelpers.ToHex(sha256.ComputeHash(fileStream)).ToLowerInvariant()); + } + } + } +} diff --git a/WalletWasabi.Tests/UnitTests/BitcoinCore/P2pBasedTests.cs b/WalletWasabi.Tests/UnitTests/BitcoinCore/P2pBasedTests.cs index dec844dc6d4..3842f0f4ea3 100644 --- a/WalletWasabi.Tests/UnitTests/BitcoinCore/P2pBasedTests.cs +++ b/WalletWasabi.Tests/UnitTests/BitcoinCore/P2pBasedTests.cs @@ -14,6 +14,7 @@ using WalletWasabi.Services; using WalletWasabi.Stores; using WalletWasabi.Tests.Helpers; +using WalletWasabi.Wallets; using Xunit; namespace WalletWasabi.Tests.UnitTests.BitcoinCore @@ -31,8 +32,16 @@ public async Task MempoolNotifiesAsync() { var network = coreNode.Network; var rpc = coreNode.RpcClient; + + var walletName = "wallet.dat"; + await rpc.CreateWalletAsync(walletName); + var dir = Path.Combine(Global.Instance.DataDir, EnvironmentHelpers.GetCallerFileName(), EnvironmentHelpers.GetMethodName()); - var bitcoinStore = new BitcoinStore(dir, network, new IndexStore(network, new SmartHeaderChain()), new AllTransactionStore(), new MempoolService()); + var indexStore = new IndexStore(Path.Combine(dir, "indexStore"), network, new SmartHeaderChain()); + var transactionStore = new AllTransactionStore(Path.Combine(dir, "transactionStore"), network); + var mempoolService = new MempoolService(); + var blocks = new FileSystemBlockRepository(Path.Combine(dir, "blocks"), network); + var bitcoinStore = new BitcoinStore(indexStore, transactionStore, mempoolService, blocks); await bitcoinStore.InitializeAsync(); await rpc.GenerateAsync(101); @@ -82,6 +91,10 @@ public async Task TrustedNotifierNotifiesTxAsync() try { var rpc = coreNode.RpcClient; + + var walletName = "wallet.dat"; + await rpc.CreateWalletAsync(walletName); + await rpc.GenerateAsync(101); var network = rpc.Network; @@ -129,6 +142,10 @@ public async Task BlockNotifierTestsAsync() try { var rpc = coreNode.RpcClient; + + var walletName = "wallet.dat"; + await rpc.CreateWalletAsync(walletName); + BlockNotifier notifier = services.FirstOrDefault(); // Make sure we get notification for one block. diff --git a/WalletWasabi.Tests/UnitTests/BitcoinCore/RpcBasedTests.cs b/WalletWasabi.Tests/UnitTests/BitcoinCore/RpcBasedTests.cs index f662f705e17..bb943ef13c0 100644 --- a/WalletWasabi.Tests/UnitTests/BitcoinCore/RpcBasedTests.cs +++ b/WalletWasabi.Tests/UnitTests/BitcoinCore/RpcBasedTests.cs @@ -192,6 +192,9 @@ public async Task CantDoubleSpendAsync() var rpc = coreNode.RpcClient; var network = rpc.Network; + var walletName = "wallet.dat"; + await rpc.CreateWalletAsync(walletName); + var key = new Key(); var blockId = await rpc.GenerateToAddressAsync(1, key.PubKey.WitHash.GetAddress(network)); var block = await rpc.GetBlockAsync(blockId[0]); diff --git a/WalletWasabi.Tests/UnitTests/Filters/IndexStoreTests.cs b/WalletWasabi.Tests/UnitTests/Filters/IndexStoreTests.cs index 49ac5c9a8c2..009d8ec880d 100644 --- a/WalletWasabi.Tests/UnitTests/Filters/IndexStoreTests.cs +++ b/WalletWasabi.Tests/UnitTests/Filters/IndexStoreTests.cs @@ -22,14 +22,14 @@ public class IndexStoreTests public async Task IndexStoreTestsAsync() { var network = Network.Main; - var indexStore = new IndexStore(network, new SmartHeaderChain()); var dir = (await GetIndexStorePathsAsync()).dir; if (Directory.Exists(dir)) { Directory.Delete(dir, true); } - await indexStore.InitializeAsync(dir); + var indexStore = new IndexStore(dir, network, new SmartHeaderChain()); + await indexStore.InitializeAsync(); } [Fact] @@ -40,7 +40,7 @@ public async Task InconsistentMatureIndexAsync() var network = Network.Main; var headersChain = new SmartHeaderChain(); - var indexStore = new IndexStore(network, headersChain); + var indexStore = new IndexStore(dir, network, headersChain); var dummyFilter = GolombRiceFilter.Parse("00"); static DateTimeOffset MinutesAgo(int mins) => DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(mins)); @@ -52,7 +52,7 @@ public async Task InconsistentMatureIndexAsync() }; await File.WriteAllLinesAsync(matureFilters, matureIndexStoreContent.Select(x => x.ToLine())); - await Assert.ThrowsAsync(async () => await indexStore.InitializeAsync(dir)); + await Assert.ThrowsAsync(async () => await indexStore.InitializeAsync()); Assert.Equal(new uint256(3), headersChain.TipHash); Assert.Equal(2u, headersChain.TipHeight); @@ -67,7 +67,7 @@ public async Task InconsistentImmatureIndexAsync() var network = Network.Main; var headersChain = new SmartHeaderChain(); - var indexStore = new IndexStore(network, headersChain); + var indexStore = new IndexStore(dir, network, headersChain); var dummyFilter = GolombRiceFilter.Parse("00"); @@ -82,7 +82,7 @@ public async Task InconsistentImmatureIndexAsync() }; await File.WriteAllLinesAsync(immatureFilters, immatureIndexStoreContent.Select(x => x.ToLine())); - await Assert.ThrowsAsync(async () => await indexStore.InitializeAsync(dir)); + await Assert.ThrowsAsync(async () => await indexStore.InitializeAsync()); Assert.Equal(new uint256(3), headersChain.TipHash); Assert.Equal(startingFilter.Header.Height + 2u, headersChain.TipHeight); @@ -97,7 +97,7 @@ public async Task GapInIndexAsync() var network = Network.Main; var headersChain = new SmartHeaderChain(); - var indexStore = new IndexStore(network, headersChain); + var indexStore = new IndexStore(dir, network, headersChain); var dummyFilter = GolombRiceFilter.Parse("00"); @@ -115,7 +115,7 @@ public async Task GapInIndexAsync() }; await File.WriteAllLinesAsync(immatureFilters, immatureIndexStoreContent.Select(x => x.ToLine())); - await Assert.ThrowsAsync(async () => await indexStore.InitializeAsync(dir)); + await Assert.ThrowsAsync(async () => await indexStore.InitializeAsync()); Assert.Equal(new uint256(3), headersChain.TipHash); Assert.Equal(2u, headersChain.TipHeight); @@ -130,7 +130,7 @@ public async Task ReceiveNonMatchingFilterAsync() var network = Network.Main; var headersChain = new SmartHeaderChain(); - var indexStore = new IndexStore(network, headersChain); + var indexStore = new IndexStore(dir, network, headersChain); var dummyFilter = GolombRiceFilter.Parse("00"); @@ -142,7 +142,7 @@ public async Task ReceiveNonMatchingFilterAsync() }; await File.WriteAllLinesAsync(matureFilters, matureIndexStoreContent.Select(x => x.ToLine())); - await indexStore.InitializeAsync(dir); + await indexStore.InitializeAsync(); Assert.Equal(new uint256(3), headersChain.TipHash); Assert.Equal(2u, headersChain.TipHeight); diff --git a/WalletWasabi.Tests/UnitTests/Hwi/HwiBinaryHashesTests.cs b/WalletWasabi.Tests/UnitTests/Hwi/HwiBinaryHashesTests.cs new file mode 100644 index 00000000000..322fedb6b7b --- /dev/null +++ b/WalletWasabi.Tests/UnitTests/Hwi/HwiBinaryHashesTests.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Threading; +using WalletWasabi.Microservices; +using Xunit; + +namespace WalletWasabi.Tests.UnitTests.Hwi +{ + public class HwiBinaryHashesTests + { + /// + /// Verifies HWI binaries distributed with Wasabi Wallet against checksums on https://github.com/bitcoin-core/HWI/releases/. + /// + /// Our current HWI version is 2.0.2. + [Fact] + public void VerifyHwiBinaryChecksumHashes() + { + using var cts = new CancellationTokenSource(5_000); + + Dictionary expectedHashes = new Dictionary() + { + { OSPlatform.Windows, "9e18cdb7c965541eb5d9a308c9360c3cd808ff3aa69bca740d172f73c6e797e7" }, + { OSPlatform.Linux, "6bdcb40c3b653fdba20ed8847ee69994c1c2e9f4e1ad2a50fcc3ed53f2c5dd66" }, + { OSPlatform.OSX, "7e7fbb907595e8718abe7aa297ee6a5d7fd0cfcd06a6913f51f7710df3a2c25e" }, + }; + + foreach (var item in expectedHashes) + { + string binaryFolder = MicroserviceHelpers.GetBinaryFolder(item.Key); + string filePath = Path.Combine(binaryFolder, item.Key == OSPlatform.Windows ? "hwi.exe" : "hwi"); + + using SHA256 sha256 = SHA256.Create(); + using FileStream fileStream = File.OpenRead(filePath); + Assert.Equal(item.Value, ByteHelpers.ToHex(sha256.ComputeHash(fileStream)).ToLowerInvariant()); + } + } + } +} diff --git a/WalletWasabi.Tests/UnitTests/Hwi/HwiProcessBridgeMock.cs b/WalletWasabi.Tests/UnitTests/Hwi/HwiProcessBridgeMock.cs index c999faff37b..1610adc439a 100644 --- a/WalletWasabi.Tests/UnitTests/Hwi/HwiProcessBridgeMock.cs +++ b/WalletWasabi.Tests/UnitTests/Hwi/HwiProcessBridgeMock.cs @@ -50,6 +50,11 @@ public HwiProcessBridgeMock(HardwareWalletModels model) model = "ledger_nano_s"; rawPath = "\\\\\\\\?\\\\hid#vid_2c97&pid_0001&mi_00#7&e45ae20&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}"; } + else if (Model == HardwareWalletModels.Ledger_Nano_X) + { + model = "ledger_nano_x"; + rawPath = "\\\\\\\\?\\\\hid#vid_2c97&pid_0001&mi_00#7&e45ae20&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}"; + } else { throw new NotImplementedException("Mock missing."); @@ -81,6 +86,10 @@ public HwiProcessBridgeMock(HardwareWalletModels model) { response = $"[{{\"model\": \"{model}\", \"path\": \"{rawPath}\", \"fingerprint\": \"4054d6f6\", \"needs_pin_sent\": false, \"needs_passphrase_sent\": false}}]\r\n"; } + else if (Model == HardwareWalletModels.Ledger_Nano_X) + { + response = $"[{{\"model\": \"{model}\", \"path\": \"{rawPath}\", \"fingerprint\": \"4054d6f6\", \"needs_pin_sent\": false, \"needs_passphrase_sent\": false}}]\r\n"; + } } else if (CompareArguments(arguments, $"{devicePathAndTypeArgumentString} wipe")) { @@ -96,6 +105,10 @@ public HwiProcessBridgeMock(HardwareWalletModels model) { response = "{\"error\": \"The Ledger Nano S does not support wiping via software\", \"code\": -9}\r\n"; } + else if (Model == HardwareWalletModels.Ledger_Nano_X) + { + response = "{\"error\": \"The Ledger Nano X does not support wiping via software\", \"code\": -9}\r\n"; + } } else if (CompareArguments(arguments, $"{devicePathAndTypeArgumentString} setup")) { @@ -111,6 +124,10 @@ public HwiProcessBridgeMock(HardwareWalletModels model) { response = "{\"error\": \"The Ledger Nano S does not support software setup\", \"code\": -9}\r\n"; } + else if (Model == HardwareWalletModels.Ledger_Nano_X) + { + response = "{\"error\": \"The Ledger Nano X does not support software setup\", \"code\": -9}\r\n"; + } } else if (CompareArguments(arguments, $"{devicePathAndTypeArgumentString} --interactive setup")) { @@ -126,6 +143,10 @@ public HwiProcessBridgeMock(HardwareWalletModels model) { response = "{\"error\": \"The Ledger Nano S does not support software setup\", \"code\": -9}\r\n"; } + else if (Model == HardwareWalletModels.Ledger_Nano_X) + { + response = "{\"error\": \"The Ledger Nano X does not support software setup\", \"code\": -9}\r\n"; + } } else if (CompareArguments(arguments, $"{devicePathAndTypeArgumentString} --interactive restore")) { @@ -141,6 +162,10 @@ public HwiProcessBridgeMock(HardwareWalletModels model) { response = "{\"error\": \"The Ledger Nano S does not support restoring via software\", \"code\": -9}\r\n"; } + else if (Model == HardwareWalletModels.Ledger_Nano_X) + { + response = "{\"error\": \"The Ledger Nano X does not support restoring via software\", \"code\": -9}\r\n"; + } } else if (CompareArguments(arguments, $"{devicePathAndTypeArgumentString} promptpin")) { @@ -156,6 +181,10 @@ public HwiProcessBridgeMock(HardwareWalletModels model) { response = "{\"error\": \"The Ledger Nano S does not need a PIN sent from the host\", \"code\": -9}\r\n"; } + else if (Model == HardwareWalletModels.Ledger_Nano_X) + { + response = "{\"error\": \"The Ledger Nano X does not need a PIN sent from the host\", \"code\": -9}\r\n"; + } } else if (CompareArguments(arguments, $"{devicePathAndTypeArgumentString} sendpin", true)) { @@ -171,26 +200,30 @@ public HwiProcessBridgeMock(HardwareWalletModels model) { response = "{\"error\": \"The Ledger Nano S does not need a PIN sent from the host\", \"code\": -9}\r\n"; } + else if (Model == HardwareWalletModels.Ledger_Nano_X) + { + response = "{\"error\": \"The Ledger Nano X does not need a PIN sent from the host\", \"code\": -9}\r\n"; + } } else if (CompareGetXbpubArguments(arguments, out string xpub)) { - if (Model == HardwareWalletModels.Trezor_T || Model == HardwareWalletModels.Coldcard || Model == HardwareWalletModels.Trezor_1 || Model == HardwareWalletModels.Ledger_Nano_S) + if (Model == HardwareWalletModels.Trezor_T || Model == HardwareWalletModels.Coldcard || Model == HardwareWalletModels.Trezor_1 || Model == HardwareWalletModels.Ledger_Nano_S || Model == HardwareWalletModels.Ledger_Nano_X) { response = $"{{\"xpub\": \"{xpub}\"}}\r\n"; } } - else if (CompareArguments(out bool t1, arguments, $"{devicePathAndTypeArgumentString} displayaddress --path m/84h/0h/0h --wpkh", false)) + else if (CompareArguments(out bool t1, arguments, $"{devicePathAndTypeArgumentString} displayaddress --path m/84h/0h/0h --addr-type wit", false)) { - if (Model == HardwareWalletModels.Trezor_T || Model == HardwareWalletModels.Coldcard || Model == HardwareWalletModels.Trezor_1 || Model == HardwareWalletModels.Ledger_Nano_S) + if (Model == HardwareWalletModels.Trezor_T || Model == HardwareWalletModels.Coldcard || Model == HardwareWalletModels.Trezor_1 || Model == HardwareWalletModels.Ledger_Nano_S || Model == HardwareWalletModels.Ledger_Nano_X) { response = t1 ? "{\"address\": \"tb1q7zqqsmqx5ymhd7qn73lm96w5yqdkrmx7rtzlxy\"}\r\n" : "{\"address\": \"bc1q7zqqsmqx5ymhd7qn73lm96w5yqdkrmx7fdevah\"}\r\n"; } } - else if (CompareArguments(out bool t2, arguments, $"{devicePathAndTypeArgumentString} displayaddress --path m/84h/0h/0h/1 --wpkh", false)) + else if (CompareArguments(out bool t2, arguments, $"{devicePathAndTypeArgumentString} displayaddress --path m/84h/0h/0h/1 --addr-type wit", false)) { - if (Model == HardwareWalletModels.Trezor_T || Model == HardwareWalletModels.Coldcard || Model == HardwareWalletModels.Trezor_1 || Model == HardwareWalletModels.Ledger_Nano_S) + if (Model == HardwareWalletModels.Trezor_T || Model == HardwareWalletModels.Coldcard || Model == HardwareWalletModels.Trezor_1 || Model == HardwareWalletModels.Ledger_Nano_S || Model == HardwareWalletModels.Ledger_Nano_X) { response = t2 ? "{\"address\": \"tb1qmaveee425a5xjkjcv7m6d4gth45jvtnjqhj3l6\"}\r\n" @@ -205,7 +238,7 @@ public HwiProcessBridgeMock(HardwareWalletModels model) private static bool CompareArguments(out bool isTestNet, string arguments, string desired, bool useStartWith = false) { - var testnetDesired = $"--testnet {desired}"; + var testnetDesired = $"--chain test {desired}"; isTestNet = false; if (useStartWith) diff --git a/WalletWasabi.Tests/UnitTests/Hwi/MockedDeviceTests.cs b/WalletWasabi.Tests/UnitTests/Hwi/MockedDeviceTests.cs index 4e413589525..7760157261a 100644 --- a/WalletWasabi.Tests/UnitTests/Hwi/MockedDeviceTests.cs +++ b/WalletWasabi.Tests/UnitTests/Hwi/MockedDeviceTests.cs @@ -341,6 +341,86 @@ public async Task LedgerNanoSTestsAsync(Network network) Assert.Equal(expectedAddress2, address2); } + [Theory] + [MemberData(nameof(GetDifferentNetworkValues))] + public async Task LedgerNanoXTestsAsync(Network network) + { + var client = new HwiClient(network, new HwiProcessBridgeMock(HardwareWalletModels.Ledger_Nano_X)); + + using var cts = new CancellationTokenSource(ReasonableRequestTimeout); + IEnumerable enumerate = await client.EnumerateAsync(cts.Token); + Assert.Single(enumerate); + HwiEnumerateEntry entry = enumerate.Single(); + Assert.Equal(HardwareWalletModels.Ledger_Nano_X, entry.Model); + Assert.Equal(@"\\?\hid#vid_2c97&pid_0001&mi_00#7&e45ae20&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}", entry.Path); + Assert.False(entry.NeedsPassphraseSent); + Assert.False(entry.NeedsPinSent); + Assert.Null(entry.Error); + Assert.Null(entry.Code); + Assert.True(entry.IsInitialized()); + Assert.Equal("4054d6f6", entry.Fingerprint.ToString()); + + var deviceType = entry.Model; + var devicePath = entry.Path; + + var wipe = await Assert.ThrowsAsync(async () => await client.WipeAsync(deviceType, devicePath, cts.Token)); + Assert.Equal("The Ledger Nano X does not support wiping via software", wipe.Message); + Assert.Equal(HwiErrorCode.UnavailableAction, wipe.ErrorCode); + + var setup = await Assert.ThrowsAsync(async () => await client.SetupAsync(deviceType, devicePath, false, cts.Token)); + Assert.Equal("The Ledger Nano X does not support software setup", setup.Message); + Assert.Equal(HwiErrorCode.UnavailableAction, setup.ErrorCode); + + var restore = await Assert.ThrowsAsync(async () => await client.RestoreAsync(deviceType, devicePath, false, cts.Token)); + Assert.Equal("The Ledger Nano X does not support restoring via software", restore.Message); + Assert.Equal(HwiErrorCode.UnavailableAction, restore.ErrorCode); + + var promptpin = await Assert.ThrowsAsync(async () => await client.PromptPinAsync(deviceType, devicePath, cts.Token)); + Assert.Equal("The Ledger Nano X does not need a PIN sent from the host", promptpin.Message); + Assert.Equal(HwiErrorCode.UnavailableAction, promptpin.ErrorCode); + + var sendpin = await Assert.ThrowsAsync(async () => await client.SendPinAsync(deviceType, devicePath, 1111, cts.Token)); + Assert.Equal("The Ledger Nano X does not need a PIN sent from the host", sendpin.Message); + Assert.Equal(HwiErrorCode.UnavailableAction, sendpin.ErrorCode); + + KeyPath keyPath1 = KeyManager.DefaultAccountKeyPath; + KeyPath keyPath2 = KeyManager.DefaultAccountKeyPath.Derive(1); + ExtPubKey xpub1 = await client.GetXpubAsync(deviceType, devicePath, keyPath1, cts.Token); + ExtPubKey xpub2 = await client.GetXpubAsync(deviceType, devicePath, keyPath2, cts.Token); + var expecteXpub1 = NBitcoinHelpers.BetterParseExtPubKey("xpub6DHjDx4gzLV37gJWMxYJAqyKRGN46MT61RHVizdU62cbVUYu9L95cXKzX62yJ2hPbN11EeprS8sSn8kj47skQBrmycCMzFEYBQSntVKFQ5M"); + var expecteXpub2 = NBitcoinHelpers.BetterParseExtPubKey("xpub6FJS1ne3STcKdQ9JLXNzZXidmCNZ9dxLiy7WVvsRkcmxjJsrDKJKEAXq4MGyEBM3vHEw2buqXezfNK5SNBrkwK7Fxjz1TW6xzRr2pUyMWFu"); + Assert.Equal(expecteXpub1, xpub1); + Assert.Equal(expecteXpub2, xpub2); + + BitcoinWitPubKeyAddress address1 = await client.DisplayAddressAsync(deviceType, devicePath, keyPath1, cts.Token); + BitcoinWitPubKeyAddress address2 = await client.DisplayAddressAsync(deviceType, devicePath, keyPath2, cts.Token); + + BitcoinAddress expectedAddress1; + BitcoinAddress expectedAddress2; + if (network == Network.Main) + { + expectedAddress1 = BitcoinAddress.Create("bc1q7zqqsmqx5ymhd7qn73lm96w5yqdkrmx7fdevah", Network.Main); + expectedAddress2 = BitcoinAddress.Create("bc1qmaveee425a5xjkjcv7m6d4gth45jvtnj23fzyf", Network.Main); + } + else if (network == Network.TestNet) + { + expectedAddress1 = BitcoinAddress.Create("tb1q7zqqsmqx5ymhd7qn73lm96w5yqdkrmx7rtzlxy", Network.TestNet); + expectedAddress2 = BitcoinAddress.Create("tb1qmaveee425a5xjkjcv7m6d4gth45jvtnjqhj3l6", Network.TestNet); + } + else if (network == Network.RegTest) + { + expectedAddress1 = BitcoinAddress.Create("bcrt1q7zqqsmqx5ymhd7qn73lm96w5yqdkrmx7pzmj3d", Network.RegTest); + expectedAddress2 = BitcoinAddress.Create("bcrt1qmaveee425a5xjkjcv7m6d4gth45jvtnjz7tugn", Network.RegTest); + } + else + { + throw new NotSupportedNetworkException(network); + } + + Assert.Equal(expectedAddress1, address1); + Assert.Equal(expectedAddress2, address2); + } + #endregion Tests #region HelperMethods diff --git a/WalletWasabi.Tests/UnitTests/MockRpcClient.cs b/WalletWasabi.Tests/UnitTests/MockRpcClient.cs index 03837b181ce..860086cd596 100644 --- a/WalletWasabi.Tests/UnitTests/MockRpcClient.cs +++ b/WalletWasabi.Tests/UnitTests/MockRpcClient.cs @@ -173,5 +173,10 @@ public Task GenerateToAddressAsync(int nBlocks, BitcoinAddress addres { throw new NotImplementedException(); } + + public Task CreateWalletAsync(string walletNameOrPath, CreateWalletOptions? options = null) + { + throw new NotImplementedException(); + } } } diff --git a/WalletWasabi.Tests/UnitTests/Transactions/AllTransactionStoreTests.cs b/WalletWasabi.Tests/UnitTests/Transactions/AllTransactionStoreTests.cs index 86892869ccc..0c61d0d1334 100644 --- a/WalletWasabi.Tests/UnitTests/Transactions/AllTransactionStoreTests.cs +++ b/WalletWasabi.Tests/UnitTests/Transactions/AllTransactionStoreTests.cs @@ -77,9 +77,9 @@ public static IEnumerable GetDifferentNetworkValues() [MemberData(nameof(GetDifferentNetworkValues))] public async Task CanInitializeEmptyAsync(Network network) { - var txStore = new AllTransactionStore(); var dir = PrepareWorkDir(); - await txStore.InitializeAsync(dir, network, ensureBackwardsCompatibility: false); + var txStore = new AllTransactionStore(dir, network); + await txStore.InitializeAsync(ensureBackwardsCompatibility: false); Assert.NotNull(txStore.ConfirmedStore); Assert.NotNull(txStore.MempoolStore); @@ -138,8 +138,8 @@ public async Task CanInitializeAsync() await File.WriteAllLinesAsync(mempoolFile, mempoolFileContent); await File.WriteAllLinesAsync(txFile, txFileContent); - var txStore = new AllTransactionStore(); - await txStore.InitializeAsync(dir, network, ensureBackwardsCompatibility: false); + var txStore = new AllTransactionStore(dir, network); + await txStore.InitializeAsync(ensureBackwardsCompatibility: false); Assert.Equal(6, txStore.GetTransactions().Count()); Assert.Equal(6, txStore.GetTransactionHashes().Count()); @@ -196,8 +196,8 @@ public async Task CorrectsMempoolConfSeparateDupAsync() await File.WriteAllLinesAsync(mempoolFile, mempoolFileContent); await File.WriteAllLinesAsync(txFile, txFileContent); - var txStore = new AllTransactionStore(); - await txStore.InitializeAsync(dir, network, ensureBackwardsCompatibility: false); + var txStore = new AllTransactionStore(dir, network); + await txStore.InitializeAsync(ensureBackwardsCompatibility: false); Assert.Equal(6, txStore.GetTransactions().Count()); Assert.Equal(6, txStore.GetTransactionHashes().Count()); @@ -231,8 +231,8 @@ public async Task CorrectsLabelDupAsync() await File.WriteAllLinesAsync(mempoolFile, mempoolFileContent); await File.WriteAllLinesAsync(txFile, txFileContent); - var txStore = new AllTransactionStore(); - await txStore.InitializeAsync(dir, network, ensureBackwardsCompatibility: false); + var txStore = new AllTransactionStore(dir, network); + await txStore.InitializeAsync(ensureBackwardsCompatibility: false); Assert.Equal(6, txStore.GetTransactions().Count()); Assert.Equal(2, txStore.MempoolStore.GetTransactions().Count()); @@ -269,8 +269,8 @@ public async Task CorrectsMempoolConfBetweenDupAsync() await File.WriteAllLinesAsync(mempoolFile, mempoolFileContent); await File.WriteAllLinesAsync(txFile, txFileContent); - var txStore = new AllTransactionStore(); - await txStore.InitializeAsync(dir, network, ensureBackwardsCompatibility: false); + var txStore = new AllTransactionStore(dir, network); + await txStore.InitializeAsync(ensureBackwardsCompatibility: false); Assert.Equal(6, txStore.GetTransactions().Count()); Assert.Equal(2, txStore.MempoolStore.GetTransactions().Count()); @@ -310,8 +310,8 @@ public async Task CorrectsOrderAsync() await File.WriteAllLinesAsync(mempoolFile, mempoolFileContent); await File.WriteAllLinesAsync(txFile, txFileContent); - var txStore = new AllTransactionStore(); - await txStore.InitializeAsync(dir, network, ensureBackwardsCompatibility: false); + var txStore = new AllTransactionStore(dir, network); + await txStore.InitializeAsync(ensureBackwardsCompatibility: false); var txs = txStore.GetTransactions(); var txHashes = txStore.GetTransactionHashes(); @@ -328,8 +328,8 @@ public async Task CorrectsOrderAsync() Assert.Equal(txHashes, txs.Select(x => x.GetHash())); Assert.Equal(expectedArray, txs); - txStore = new AllTransactionStore(); - await txStore.InitializeAsync(PrepareWorkDir(), network, ensureBackwardsCompatibility: false); + txStore = new AllTransactionStore(PrepareWorkDir(), network); + await txStore.InitializeAsync(ensureBackwardsCompatibility: false); txStore.AddOrUpdate(uTx3); txStore.AddOrUpdate(uTx1); @@ -349,8 +349,8 @@ public async Task CorrectsOrderAsync() [MemberData(nameof(GetDifferentNetworkValues))] public async Task DoesntUpdateAsync(Network network) { - var txStore = new AllTransactionStore(); - await txStore.InitializeAsync(PrepareWorkDir(), network, ensureBackwardsCompatibility: false); + var txStore = new AllTransactionStore(PrepareWorkDir(), network); + await txStore.InitializeAsync(ensureBackwardsCompatibility: false); var tx = Global.GenerateRandomSmartTransaction(); Assert.False(txStore.TryUpdate(tx)); @@ -402,8 +402,8 @@ public async Task ReorgAsync() await File.WriteAllLinesAsync(mempoolFile, mempoolFileContent); await File.WriteAllLinesAsync(txFile, txFileContent); - var txStore = new AllTransactionStore(); - await txStore.InitializeAsync(dir, network, ensureBackwardsCompatibility: false); + var txStore = new AllTransactionStore(dir, network); + await txStore.InitializeAsync(ensureBackwardsCompatibility: false); // Two transactions are in the mempool store and unconfirmed. Assert.True(txStore.MempoolStore.TryGetTransaction(uTx1.GetHash(), out SmartTransaction myUnconfirmedTx1)); @@ -447,9 +447,9 @@ public async Task ReorgSameBlockAgainAsync() int transactionsPerBlock = 3; string dir = PrepareWorkDir(); - var txStore = new AllTransactionStore(); var network = Network.Main; - await txStore.InitializeAsync(dir, network, ensureBackwardsCompatibility: false); + var txStore = new AllTransactionStore(dir, network); + await txStore.InitializeAsync(ensureBackwardsCompatibility: false); foreach (var height in Enumerable.Range(1, blocks)) { diff --git a/WalletWasabi.Tests/UnitTests/Transactions/PayjoinTests.cs b/WalletWasabi.Tests/UnitTests/Transactions/PayjoinTests.cs index 0a9aa5df24b..703935d45c6 100644 --- a/WalletWasabi.Tests/UnitTests/Transactions/PayjoinTests.cs +++ b/WalletWasabi.Tests/UnitTests/Transactions/PayjoinTests.cs @@ -459,14 +459,13 @@ private TransactionFactory CreateTransactionFactory( { foreach (var sameLabelCoin in scoins.Where(c => !c.Label.IsEmpty && c.Label == coin.Label)) { - sameLabelCoin.Clusters = coin.Clusters; + sameLabelCoin.Cluster = coin.Cluster; } } var coinsView = new CoinsView(scoins); + var transactionStore = new AllTransactionStoreMock(workFolderPath: ".", Network.Main); - var bitcoinStore = new BitcoinStoreMock(); - - return new TransactionFactory(Network.Main, keyManager, coinsView, bitcoinStore, password, allowUnconfirmed); + return new TransactionFactory(Network.Main, keyManager, coinsView, transactionStore, password, allowUnconfirmed); } } } diff --git a/WalletWasabi.Tests/UnitTests/Transactions/TransactionFactoryTests.cs b/WalletWasabi.Tests/UnitTests/Transactions/TransactionFactoryTests.cs index 5cf8f2c8f28..a3ab09bf03d 100644 --- a/WalletWasabi.Tests/UnitTests/Transactions/TransactionFactoryTests.cs +++ b/WalletWasabi.Tests/UnitTests/Transactions/TransactionFactoryTests.cs @@ -205,7 +205,7 @@ public void SelectSameClusterCoins() var cluster1 = new Cluster(coinsCluster1); foreach (var coin in coinsCluster1) { - coin.Clusters = cluster1; + coin.Cluster = cluster1; } // cluster 2 is known by 6 people: Julio, Lee, Jean, Donald, Onur and Satoshi @@ -213,14 +213,12 @@ public void SelectSameClusterCoins() var cluster2 = new Cluster(coinsCluster2); foreach (var coin in coinsCluster2) { - coin.Clusters = cluster2; + coin.Cluster = cluster2; } var coinsView = new CoinsView(scoins.ToArray()); - - var bitcoinStore = new BitcoinStoreMock(); - - var transactionFactory = new TransactionFactory(Network.Main, keyManager, coinsView, bitcoinStore, password); + var transactionStore = new AllTransactionStoreMock(workFolderPath: ".", Network.Main); + var transactionFactory = new TransactionFactory(Network.Main, keyManager, coinsView, transactionStore, password); // Two 0.9btc coins are enough var payment = new PaymentIntent(new Key().ScriptPubKey, Money.Coins(1.75m), label: "Sophie"); @@ -228,7 +226,7 @@ public void SelectSameClusterCoins() var result = transactionFactory.BuildTransaction(payment, feeRate); Assert.Equal(2, result.SpentCoins.Count()); - Assert.All(result.SpentCoins, c => Assert.Equal(c.Clusters, cluster2)); + Assert.All(result.SpentCoins, c => Assert.Equal(c.Cluster, cluster2)); Assert.Contains(coinsByLabel["Julio"], result.SpentCoins); Assert.Contains(coinsByLabel["Donald, Jean, Lee, Onur"], result.SpentCoins); @@ -238,7 +236,7 @@ public void SelectSameClusterCoins() result = transactionFactory.BuildTransaction(payment, feeRate); Assert.Equal(3, result.SpentCoins.Count()); - Assert.All(result.SpentCoins, c => Assert.Equal(c.Clusters, cluster2)); + Assert.All(result.SpentCoins, c => Assert.Equal(c.Cluster, cluster2)); Assert.Contains(coinsByLabel["Julio"], result.SpentCoins); Assert.Contains(coinsByLabel["Donald, Jean, Lee, Onur"], result.SpentCoins); Assert.Contains(coinsByLabel["Satoshi"], result.SpentCoins); @@ -250,7 +248,7 @@ public void SelectSameClusterCoins() result = transactionFactory.BuildTransaction(payment, feeRate); Assert.Equal(4, result.SpentCoins.Count()); - Assert.All(result.SpentCoins, c => Assert.Equal(c.Clusters, cluster1)); + Assert.All(result.SpentCoins, c => Assert.Equal(c.Cluster, cluster1)); Assert.Contains(coinsByLabel["Pablo"], result.SpentCoins); Assert.Contains(coinsByLabel["Daniel"], result.SpentCoins); Assert.Contains(coinsByLabel["Adolf"], result.SpentCoins); @@ -618,16 +616,12 @@ private TransactionFactory CreateTransactionFactory( { foreach (var sameLabelCoin in scoins.Where(c => !c.Label.IsEmpty && c.Label == coin.Label)) { - sameLabelCoin.Clusters = coin.Clusters; + sameLabelCoin.Cluster = coin.Cluster; } } var coinsView = new CoinsView(scoins); - - var bitcoinStore = new BitcoinStoreMock(); - - var transactionFactory = new TransactionFactory(Network.Main, keyManager, coinsView, bitcoinStore, password); - - return new TransactionFactory(Network.Main, keyManager, coinsView, bitcoinStore, password, allowUnconfirmed); + var transactionStore = new AllTransactionStoreMock(workFolderPath: ".", Network.Main); + return new TransactionFactory(Network.Main, keyManager, coinsView, transactionStore, password, allowUnconfirmed); } } } diff --git a/WalletWasabi.Tests/UnitTests/Transactions/TransactionProcessorTests.cs b/WalletWasabi.Tests/UnitTests/Transactions/TransactionProcessorTests.cs index 6e52d79cfef..61167377f80 100644 --- a/WalletWasabi.Tests/UnitTests/Transactions/TransactionProcessorTests.cs +++ b/WalletWasabi.Tests/UnitTests/Transactions/TransactionProcessorTests.cs @@ -743,14 +743,14 @@ public async Task SimpleDirectClusteringAsync() transactionProcessor.Process(tx0); var createdCoin = transactionProcessor.Coins.First(); - Assert.Equal("A", createdCoin.Clusters.Labels); + Assert.Equal("A", createdCoin.Cluster.Labels); // Spend the received coin to someone else B var changeScript1 = transactionProcessor.NewKey("B").P2wpkhScript; var tx1 = CreateSpendingTransaction(new[] { createdCoin.GetCoin() }, new Key().ScriptPubKey, changeScript1); transactionProcessor.Process(tx1); createdCoin = transactionProcessor.Coins.First(); - Assert.Equal("A, B", createdCoin.Clusters.Labels); + Assert.Equal("A, B", createdCoin.Cluster.Labels); // Spend the received coin to myself else C var myselfScript = transactionProcessor.NewKey("C").P2wpkhScript; @@ -760,10 +760,10 @@ public async Task SimpleDirectClusteringAsync() Assert.Equal(2, transactionProcessor.Coins.Count()); createdCoin = transactionProcessor.Coins.First(); - Assert.Equal("A, B, C", createdCoin.Clusters.Labels); + Assert.Equal("A, B, C", createdCoin.Cluster.Labels); var createdchangeCoin = transactionProcessor.Coins.Last(); - Assert.Equal("A, B, C", createdchangeCoin.Clusters.Labels); + Assert.Equal("A, B, C", createdchangeCoin.Cluster.Labels); } [Fact] @@ -791,9 +791,9 @@ public async Task MultipleDirectClusteringAsync() var tx2 = CreateCreditingTransaction(key.P2wpkhScript, Money.Coins(1.0m)); transactionProcessor.Process(tx2); - var scoinA = Assert.Single(transactionProcessor.Coins, c => c.Clusters.Labels == "A"); - var scoinB = Assert.Single(transactionProcessor.Coins, c => c.Clusters.Labels == "B"); - var scoinC = Assert.Single(transactionProcessor.Coins, c => c.Clusters.Labels == "C"); + var scoinA = Assert.Single(transactionProcessor.Coins, c => c.Cluster.Labels == "A"); + var scoinB = Assert.Single(transactionProcessor.Coins, c => c.Cluster.Labels == "B"); + var scoinC = Assert.Single(transactionProcessor.Coins, c => c.Cluster.Labels == "C"); var changeScript = transactionProcessor.NewKey("D").P2wpkhScript; var coins = new[] { scoinA.GetCoin(), scoinB.GetCoin(), scoinC.GetCoin() }; @@ -801,12 +801,12 @@ public async Task MultipleDirectClusteringAsync() transactionProcessor.Process(tx3); var changeCoinD = Assert.Single(transactionProcessor.Coins); - Assert.Equal("A, B, C, D", changeCoinD.Clusters.Labels); + Assert.Equal("A, B, C, D", changeCoinD.Cluster.Labels); key = transactionProcessor.NewKey("E"); var tx4 = CreateCreditingTransaction(key.P2wpkhScript, Money.Coins(1.0m)); transactionProcessor.Process(tx4); - var scoinE = Assert.Single(transactionProcessor.Coins, c => c.Clusters.Labels == "E"); + var scoinE = Assert.Single(transactionProcessor.Coins, c => c.Cluster.Labels == "E"); changeScript = transactionProcessor.NewKey("F").P2wpkhScript; coins = new[] { changeCoinD.GetCoin(), scoinE.GetCoin() }; @@ -814,7 +814,7 @@ public async Task MultipleDirectClusteringAsync() transactionProcessor.Process(tx5); var changeCoin = Assert.Single(transactionProcessor.Coins); - Assert.Equal("A, B, C, D, E, F", changeCoin.Clusters.Labels); + Assert.Equal("A, B, C, D, E, F", changeCoin.Cluster.Labels); } [Fact] @@ -840,9 +840,9 @@ public async Task SameScriptClusteringAsync() var tx2 = CreateCreditingTransaction(key.P2wpkhScript, Money.Coins(1.0m)); transactionProcessor.Process(tx2); - var scoinA = Assert.Single(transactionProcessor.Coins, c => c.Clusters.Labels == "A"); - var scoinB = Assert.Single(transactionProcessor.Coins, c => c.Clusters.Labels == "B"); - var scoinC = Assert.Single(transactionProcessor.Coins, c => c.Clusters.Labels == "C"); + var scoinA = Assert.Single(transactionProcessor.Coins, c => c.Cluster.Labels == "A"); + var scoinB = Assert.Single(transactionProcessor.Coins, c => c.Cluster.Labels == "B"); + var scoinC = Assert.Single(transactionProcessor.Coins, c => c.Cluster.Labels == "C"); var myself = transactionProcessor.NewKey("D").P2wpkhScript; var changeScript = transactionProcessor.NewKey("").P2wpkhScript; @@ -851,13 +851,13 @@ public async Task SameScriptClusteringAsync() transactionProcessor.Process(tx3); var paymentCoin = Assert.Single(transactionProcessor.Coins, c => c.ScriptPubKey == myself); - Assert.Equal("A, B, C, D", paymentCoin.Clusters.Labels); + Assert.Equal("A, B, C, D", paymentCoin.Cluster.Labels); var tx4 = CreateCreditingTransaction(myself, Money.Coins(7.0m)); transactionProcessor.Process(tx4); Assert.Equal(2, transactionProcessor.Coins.Count(c => c.ScriptPubKey == myself)); var newPaymentCoin = Assert.Single(transactionProcessor.Coins, c => c.Amount == Money.Coins(7.0m)); - Assert.Equal("A, B, C, D", newPaymentCoin.Clusters.Labels); + Assert.Equal("A, B, C, D", newPaymentCoin.Cluster.Labels); } [Fact] @@ -882,8 +882,8 @@ public async Task SameScriptClusterAfterSpendingAsync() transactionProcessor.Process(tx2); var coins = transactionProcessor.Coins; - Assert.Equal(coins.First().Clusters, coins.Last().Clusters); - Assert.Equal("A, B", coins.First().Clusters.Labels.ToString()); + Assert.Equal(coins.First().Cluster, coins.Last().Cluster); + Assert.Equal("A, B", coins.First().Cluster.Labels.ToString()); } [Fact] @@ -908,9 +908,9 @@ public async Task SameClusterAfterReplacedByFeeAsync() var tx2 = CreateCreditingTransaction(key.P2wpkhScript, Money.Coins(1.0m)); transactionProcessor.Process(tx2); - var scoinA = Assert.Single(transactionProcessor.Coins, c => c.Clusters.Labels == "A"); - var scoinB = Assert.Single(transactionProcessor.Coins, c => c.Clusters.Labels == "B"); - var scoinC = Assert.Single(transactionProcessor.Coins, c => c.Clusters.Labels == "C"); + var scoinA = Assert.Single(transactionProcessor.Coins, c => c.Cluster.Labels == "A"); + var scoinB = Assert.Single(transactionProcessor.Coins, c => c.Cluster.Labels == "B"); + var scoinC = Assert.Single(transactionProcessor.Coins, c => c.Cluster.Labels == "C"); var myself = transactionProcessor.NewKey("D").P2wpkhScript; var changeScript = transactionProcessor.NewKey("").P2wpkhScript; @@ -919,18 +919,18 @@ public async Task SameClusterAfterReplacedByFeeAsync() transactionProcessor.Process(tx3); var paymentCoin = Assert.Single(transactionProcessor.Coins, c => c.ScriptPubKey == myself); - Assert.Equal("A, B, C, D", paymentCoin.Clusters.Labels); + Assert.Equal("A, B, C, D", paymentCoin.Cluster.Labels); coins = new[] { scoinB.GetCoin(), scoinC.GetCoin(), scoinA.GetCoin() }; var tx4 = CreateSpendingTransaction(coins, myself, changeScript); transactionProcessor.Process(tx4); paymentCoin = Assert.Single(transactionProcessor.Coins, c => c.ScriptPubKey == myself); - Assert.Equal("A, B, C, D", paymentCoin.Clusters.Labels); + Assert.Equal("A, B, C, D", paymentCoin.Cluster.Labels); } [Fact] - public async Task UpdateClustersAfterReplacedByFeeWithNewCoinsAsync() + public async Task UpdateClusterAfterReplacedByFeeWithNewCoinsAsync() { // --tx0---> (A) --+ // | @@ -952,9 +952,9 @@ public async Task UpdateClustersAfterReplacedByFeeWithNewCoinsAsync() var tx2 = CreateCreditingTransaction(key.P2wpkhScript, Money.Coins(1.0m)); transactionProcessor.Process(tx2); - var scoinA = Assert.Single(transactionProcessor.Coins, c => c.Clusters.Labels == "A"); - var scoinB = Assert.Single(transactionProcessor.Coins, c => c.Clusters.Labels == "B"); - var scoinC = Assert.Single(transactionProcessor.Coins, c => c.Clusters.Labels == "C"); + var scoinA = Assert.Single(transactionProcessor.Coins, c => c.Cluster.Labels == "A"); + var scoinB = Assert.Single(transactionProcessor.Coins, c => c.Cluster.Labels == "B"); + var scoinC = Assert.Single(transactionProcessor.Coins, c => c.Cluster.Labels == "C"); var myself = transactionProcessor.NewKey("D").P2wpkhScript; var changeScript = transactionProcessor.NewKey("").P2wpkhScript; @@ -963,19 +963,19 @@ public async Task UpdateClustersAfterReplacedByFeeWithNewCoinsAsync() transactionProcessor.Process(tx3); var paymentCoin = Assert.Single(transactionProcessor.Coins, c => c.ScriptPubKey == myself); - Assert.Equal("A, B, C, D", paymentCoin.Clusters.Labels); + Assert.Equal("A, B, C, D", paymentCoin.Cluster.Labels); key = transactionProcessor.NewKey("X"); var tx4 = CreateCreditingTransaction(key.P2wpkhScript, Money.Coins(1.0m)); transactionProcessor.Process(tx4); - var scoinX = Assert.Single(transactionProcessor.Coins, c => c.Clusters.Labels == "X"); + var scoinX = Assert.Single(transactionProcessor.Coins, c => c.Cluster.Labels == "X"); coins = new[] { scoinB.GetCoin(), scoinX.GetCoin(), scoinC.GetCoin(), scoinA.GetCoin() }; var tx5 = CreateSpendingTransaction(coins, myself, changeScript); transactionProcessor.Process(tx5); paymentCoin = Assert.Single(transactionProcessor.Coins, c => c.ScriptPubKey == myself); - Assert.Equal("A, B, C, D, X", paymentCoin.Clusters.Labels); + Assert.Equal("A, B, C, D, X", paymentCoin.Cluster.Labels); } [Fact] @@ -1001,9 +1001,9 @@ public async Task RememberClusteringAfterReorgAsync() var tx2 = CreateCreditingTransaction(key.P2wpkhScript, Money.Coins(1.0m), height: 54323); transactionProcessor.Process(tx2); - var scoinA = Assert.Single(transactionProcessor.Coins, c => c.Clusters.Labels == "A"); - var scoinB = Assert.Single(transactionProcessor.Coins, c => c.Clusters.Labels == "B"); - var scoinC = Assert.Single(transactionProcessor.Coins, c => c.Clusters.Labels == "C"); + var scoinA = Assert.Single(transactionProcessor.Coins, c => c.Cluster.Labels == "A"); + var scoinB = Assert.Single(transactionProcessor.Coins, c => c.Cluster.Labels == "B"); + var scoinC = Assert.Single(transactionProcessor.Coins, c => c.Cluster.Labels == "C"); var changeScript = transactionProcessor.NewKey("D").P2wpkhScript; var coins = new[] { scoinA.GetCoin(), scoinB.GetCoin(), scoinC.GetCoin() }; @@ -1011,20 +1011,20 @@ public async Task RememberClusteringAfterReorgAsync() transactionProcessor.Process(tx3); var changeCoinD = Assert.Single(transactionProcessor.Coins); - Assert.Equal("A, B, C, D", changeCoinD.Clusters.Labels); - Assert.Equal(scoinA.Clusters, changeCoinD.Clusters); - Assert.Equal(scoinB.Clusters, changeCoinD.Clusters); - Assert.Equal(scoinC.Clusters, changeCoinD.Clusters); + Assert.Equal("A, B, C, D", changeCoinD.Cluster.Labels); + Assert.Equal(scoinA.Cluster, changeCoinD.Cluster); + Assert.Equal(scoinB.Cluster, changeCoinD.Cluster); + Assert.Equal(scoinC.Cluster, changeCoinD.Cluster); // reorg Assert.True(changeCoinD.Confirmed); transactionProcessor.UndoBlock(tx3.Height); Assert.False(changeCoinD.Confirmed); - Assert.Equal("A, B, C, D", changeCoinD.Clusters.Labels); - Assert.Equal(scoinA.Clusters, changeCoinD.Clusters); - Assert.Equal(scoinB.Clusters, changeCoinD.Clusters); - Assert.Equal(scoinC.Clusters, changeCoinD.Clusters); + Assert.Equal("A, B, C, D", changeCoinD.Cluster.Labels); + Assert.Equal(scoinA.Cluster, changeCoinD.Cluster); + Assert.Equal(scoinB.Cluster, changeCoinD.Cluster); + Assert.Equal(scoinC.Cluster, changeCoinD.Cluster); } [Fact] @@ -1061,8 +1061,8 @@ public async Task EnoughAnonymitySetClusteringAsync() var anonymousCoin = Assert.Single(transactionProcessor.Coins, c => c.Amount == Money.Coins(0.1m)); var changeCoin = Assert.Single(transactionProcessor.Coins, c => c.Amount == Money.Coins(0.9m)); - Assert.Empty(anonymousCoin.Clusters.Labels); - Assert.NotEmpty(changeCoin.Clusters.Labels); + Assert.Empty(anonymousCoin.Cluster.Labels); + Assert.NotEmpty(changeCoin.Cluster.Labels); } private static SmartTransaction CreateSpendingTransaction(Coin coin, Script scriptPubKey = null, int height = 0) @@ -1108,10 +1108,10 @@ private async Task CreateTransactionProcessorAsync([Caller var keyManager = KeyManager.CreateNew(out _, "password"); keyManager.AssertCleanKeysIndexed(); - var txStore = new AllTransactionStore(); var dir = Path.Combine(Global.Instance.DataDir, EnvironmentHelpers.ExtractFileName(callerFilePath), callerName, "TransactionStore"); await IoHelpers.DeleteRecursivelyWithMagicDustAsync(dir); - await txStore.InitializeAsync(dir, Network.RegTest); + var txStore = new AllTransactionStore(dir, Network.RegTest); + await txStore.InitializeAsync(); return new TransactionProcessor( txStore, diff --git a/WalletWasabi.Tests/XunitConfiguration/RegTestFixture.cs b/WalletWasabi.Tests/XunitConfiguration/RegTestFixture.cs index e31d43e95cb..0dcfae8974b 100644 --- a/WalletWasabi.Tests/XunitConfiguration/RegTestFixture.cs +++ b/WalletWasabi.Tests/XunitConfiguration/RegTestFixture.cs @@ -32,6 +32,9 @@ public RegTestFixture() var hostedServices = new HostedServices(); BackendRegTestNode = TestNodeBuilder.CreateAsync(hostedServices, callerFilePath: "RegTests", callerMemberName: "BitcoinCoreData").GetAwaiter().GetResult(); + var walletName = "wallet.dat"; + BackendRegTestNode.RpcClient.CreateWalletAsync(walletName).GetAwaiter().GetResult(); + var testnetBackendDir = EnvironmentHelpers.GetDataDir(Path.Combine("WalletWasabi", "Tests", "RegTests", "Backend")); IoHelpers.DeleteRecursivelyWithMagicDustAsync(testnetBackendDir).GetAwaiter().GetResult(); Thread.Sleep(100); diff --git a/WalletWasabi.WindowsInstaller/Product.wxs b/WalletWasabi.WindowsInstaller/Product.wxs index ca75a575898..b19ce54374f 100644 --- a/WalletWasabi.WindowsInstaller/Product.wxs +++ b/WalletWasabi.WindowsInstaller/Product.wxs @@ -39,7 +39,7 @@ !(loc.Description) - https://www.reddit.com/r/WasabiWallet/ + https://github.com/zkSNACKs/WalletWasabi/discussions/5185 https://github.com/zkSNACKs/WalletWasabi/ diff --git a/WalletWasabi/BestEffortEndpointConnector.cs b/WalletWasabi/BestEffortEndpointConnector.cs new file mode 100644 index 00000000000..2c6dd133e7a --- /dev/null +++ b/WalletWasabi/BestEffortEndpointConnector.cs @@ -0,0 +1,121 @@ +using NBitcoin; +using NBitcoin.Protocol; +using NBitcoin.Protocol.Behaviors; +using NBitcoin.Protocol.Connectors; +using NBitcoin.Socks; +using System; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; +using WalletWasabi.Logging; + +namespace WalletWasabi +{ + public class BestEffortEndpointConnector : IEnpointConnector + { + public BestEffortEndpointConnector(long maxNonOnionConnectionCount) + : this(new EffortState(maxNonOnionConnectionCount)) + { + } + + private BestEffortEndpointConnector(EffortState state) + { + State = state; + } + + public EffortState State { get; private set; } + + public void UpdateConnectedNodesCounter(int connectedNodes) + { + State.ConnectedNodesCount = connectedNodes; + } + + public IEnpointConnector Clone() + { + return new BestEffortEndpointConnector(State); + } + + public virtual async Task ConnectSocket(Socket socket, EndPoint endpoint, NodeConnectionParameters nodeConnectionParameters, CancellationToken cancellationToken) + { + var isTor = endpoint.IsTor(); + + var socksSettings = nodeConnectionParameters.TemplateBehaviors.Find(); + var socketEndpoint = endpoint; + var useSocks = isTor || socksSettings?.OnlyForOnionHosts is false; + if (useSocks) + { + if (socksSettings?.SocksEndpoint == null) + { + throw new InvalidOperationException("SocksSettingsBehavior.SocksEndpoint is not set but the connection is expecting using socks proxy"); + } + if (!isTor && State.AllowOnlyTorEndpoints) + { + throw new InvalidOperationException($"The Endpoint connector is configured to allow only Tor endpoints and the '{endpoint}' enpoint is not one"); + } + + socketEndpoint = socksSettings.SocksEndpoint; + } + + if (socketEndpoint is IPEndPoint mappedv4 && mappedv4.Address.IsIPv4MappedToIPv6Ex()) + { + socketEndpoint = new IPEndPoint(mappedv4.Address.MapToIPv4Ex(), mappedv4.Port); + } + + cancellationToken.ThrowIfCancellationRequested(); + await socket.ConnectAsync(socketEndpoint).ConfigureAwait(false); + + if (useSocks) + { + await SocksHelper.Handshake(socket, endpoint, GenerateCredentials(), cancellationToken).ConfigureAwait(false); + } + } + + private NetworkCredential GenerateCredentials() + { + const string Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + var identity = new string(Enumerable.Repeat(Chars, 21) + .Select(s => s[(int)(RandomUtils.GetUInt32() % s.Length)]).ToArray()); + return new NetworkCredential(identity, identity); + } + + // A class to share state between all the BestEffortEndpointConnector connectors. + // This is necessary because NBitcoin clones the connectors for every new node connection + // attempt using the original connector. + public class EffortState + { + private bool _allowAnyConnetionType; + + public EffortState(long maxNonOnionConnectionCount) + { + MaxNonOnionConnectionCount = maxNonOnionConnectionCount; + } + + public long ConnectedNodesCount { get; set; } + + public bool AllowOnlyTorEndpoints + { + get + { + var allowAnyConnetionType = ConnectedNodesCount <= MaxNonOnionConnectionCount; + + if (_allowAnyConnetionType != allowAnyConnetionType) + { + _allowAnyConnetionType = allowAnyConnetionType; + Logger.LogDebug(ToString()); + } + + return !_allowAnyConnetionType; + } + } + + public long MaxNonOnionConnectionCount { get; } + + public override string ToString() + { + return $"Connections: {ConnectedNodesCount}, Currently allow only onions: {!_allowAnyConnetionType}."; + } + } + } +} diff --git a/WalletWasabi/BitcoinCore/IRPCClient.cs b/WalletWasabi/BitcoinCore/IRPCClient.cs index 8ff44f70cd2..850a603219b 100644 --- a/WalletWasabi/BitcoinCore/IRPCClient.cs +++ b/WalletWasabi/BitcoinCore/IRPCClient.cs @@ -70,5 +70,7 @@ public interface IRPCClient Task GetVerboseBlockAsync(uint256 blockId); Task GenerateToAddressAsync(int nBlocks, BitcoinAddress address); + + Task CreateWalletAsync(string walletNameOrPath, CreateWalletOptions? options = null); } } diff --git a/WalletWasabi/BitcoinCore/RpcClientBase.cs b/WalletWasabi/BitcoinCore/RpcClientBase.cs index f1baff510ac..026cbc6085c 100644 --- a/WalletWasabi/BitcoinCore/RpcClientBase.cs +++ b/WalletWasabi/BitcoinCore/RpcClientBase.cs @@ -173,6 +173,11 @@ public Task TryEstimateSmartFeeAsync(int confirmationT return Rpc.TryEstimateSmartFeeAsync(confirmationTarget, estimateMode: estimateMode); } + public Task CreateWalletAsync(string walletNameOrPath, CreateWalletOptions? options = null) + { + return Rpc.CreateWalletAsync(walletNameOrPath, options); + } + #endregion For Testing Only } } diff --git a/WalletWasabi/Blockchain/Analysis/Clustering/Cluster.cs b/WalletWasabi/Blockchain/Analysis/Clustering/Cluster.cs index b79ea222ae6..e719cf38aee 100644 --- a/WalletWasabi/Blockchain/Analysis/Clustering/Cluster.cs +++ b/WalletWasabi/Blockchain/Analysis/Clustering/Cluster.cs @@ -36,7 +36,7 @@ public SmartLabel Labels private List Coins { get; set; } private HashSet CoinsSet { get; set; } - public void Merge(Cluster clusters) => Merge(clusters.Coins); + public void Merge(Cluster cluster) => Merge(cluster.Coins); public void Merge(IEnumerable coins) { @@ -49,7 +49,7 @@ public void Merge(IEnumerable coins) { Coins.Insert(insertPosition++, coin); } - coin.Clusters = this; + coin.Cluster = this; } if (insertPosition > 0) // at least one element was inserted { diff --git a/WalletWasabi/Blockchain/Keys/KeyManager.cs b/WalletWasabi/Blockchain/Keys/KeyManager.cs index 4d7e48fa359..753911c7622 100644 --- a/WalletWasabi/Blockchain/Keys/KeyManager.cs +++ b/WalletWasabi/Blockchain/Keys/KeyManager.cs @@ -2,6 +2,7 @@ using Newtonsoft.Json; using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.IO; using System.Linq; using System.Security; @@ -425,7 +426,7 @@ public HdPubKey GetNextReceiveKey(SmartLabel label, out bool minGapLimitIncrease { if (label.IsEmpty) { - throw new InvalidOperationException("Known By is required."); + throw new InvalidOperationException("Label is required."); } minGapLimitIncreased = false; @@ -530,7 +531,7 @@ public IEnumerable GetPubKeyScriptBytes() { lock (HdPubKeyScriptBytesLock) { - return HdPubKeyScriptBytes; + return HdPubKeyScriptBytes.ToImmutableArray(); } } diff --git a/WalletWasabi/Blockchain/TransactionBuilding/SmartCoinSelector.cs b/WalletWasabi/Blockchain/TransactionBuilding/SmartCoinSelector.cs index 69e11e9bac2..76f23553bb1 100644 --- a/WalletWasabi/Blockchain/TransactionBuilding/SmartCoinSelector.cs +++ b/WalletWasabi/Blockchain/TransactionBuilding/SmartCoinSelector.cs @@ -32,14 +32,14 @@ public IEnumerable Select(IEnumerable coins, IMoney target) // Get unique clusters. IEnumerable uniqueClusters = UnspentCoins - .Select(coin => coin.Clusters) + .Select(coin => coin.Cluster) .Distinct(); // Build all the possible coin clusters, except when it's computationally too expensive. List> coinClusters = uniqueClusters.Count() < 10 ? uniqueClusters .CombinationsWithoutRepetition(ofLength: 1, upToLength: 6) - .Select(clusterCombination => UnspentCoins.Where(coin => clusterCombination.Contains(coin.Clusters))) + .Select(clusterCombination => UnspentCoins.Where(coin => clusterCombination.Contains(coin.Cluster))) .ToList() : new List>(); @@ -47,7 +47,7 @@ public IEnumerable Select(IEnumerable coins, IMoney target) // This operation is doing super advanced grouping on the coin clusters and adding properties to each of them. var sayajinCoinClusters = coinClusters - .Select(coins => (Coins: coins, Privacy: 1.0m / (1 + coins.Sum(x => x.Clusters.Labels.Count())))) + .Select(coins => (Coins: coins, Privacy: 1.0m / (1 + coins.Sum(x => x.Cluster.Labels.Count())))) .Select(group => new { group.Coins, diff --git a/WalletWasabi/Blockchain/TransactionOutputs/CoinsRegistry.cs b/WalletWasabi/Blockchain/TransactionOutputs/CoinsRegistry.cs index 8f1fab1b233..15567d4e695 100644 --- a/WalletWasabi/Blockchain/TransactionOutputs/CoinsRegistry.cs +++ b/WalletWasabi/Blockchain/TransactionOutputs/CoinsRegistry.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using NBitcoin; using WalletWasabi.Blockchain.Analysis.Clustering; @@ -73,7 +74,7 @@ private CoinsView AsSpentCoinsView() } } - public SmartCoin GetByOutPoint(OutPoint outpoint) => AsCoinsView().GetByOutPoint(outpoint); + public bool TryGetByOutPoint(OutPoint outpoint, [NotNullWhen(true)] out SmartCoin? coin) => AsCoinsView().TryGetByOutPoint(outpoint, out coin); public bool TryAdd(SmartCoin coin) { @@ -87,11 +88,11 @@ public bool TryAdd(SmartCoin coin) { if (ClustersByScriptPubKey.TryGetValue(coin.ScriptPubKey, out var cluster)) { - coin.Clusters = cluster; + coin.Cluster = cluster; } else { - ClustersByScriptPubKey.Add(coin.ScriptPubKey, coin.Clusters); + ClustersByScriptPubKey.Add(coin.ScriptPubKey, coin.Cluster); } foreach (var spentOutPoint in coin.SpentOutputs) @@ -175,9 +176,9 @@ public void Spend(SmartCoin spentCoin) { if (newCoin.AnonymitySet < PrivacyLevelThreshold) { - spentCoin.Clusters.Merge(newCoin.Clusters); - newCoin.Clusters = spentCoin.Clusters; - ClustersByScriptPubKey.AddOrReplace(newCoin.ScriptPubKey, newCoin.Clusters); + spentCoin.Cluster.Merge(newCoin.Cluster); + newCoin.Cluster = spentCoin.Cluster; + ClustersByScriptPubKey.AddOrReplace(newCoin.ScriptPubKey, newCoin.Cluster); } } } diff --git a/WalletWasabi/Blockchain/TransactionOutputs/CoinsView.cs b/WalletWasabi/Blockchain/TransactionOutputs/CoinsView.cs index 15aac79eefd..dcb432556f2 100644 --- a/WalletWasabi/Blockchain/TransactionOutputs/CoinsView.cs +++ b/WalletWasabi/Blockchain/TransactionOutputs/CoinsView.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using NBitcoin; using WalletWasabi.Helpers; @@ -70,7 +71,11 @@ public ICoinsView OutPoints(TxInList txIns) return new CoinsView(smartCoins); } - public SmartCoin GetByOutPoint(OutPoint outpoint) => Coins.FirstOrDefault(x => x.OutPoint == outpoint); + public bool TryGetByOutPoint(OutPoint outpoint, [NotNullWhen(true)] out SmartCoin? coin) + { + coin = Coins.FirstOrDefault(x => x.OutPoint == outpoint); + return coin is { }; + } public Money TotalAmount() => Coins.Sum(x => x.Amount); diff --git a/WalletWasabi/Blockchain/TransactionOutputs/ICoinsView.cs b/WalletWasabi/Blockchain/TransactionOutputs/ICoinsView.cs index 5ce9715edbe..580ec1935c1 100644 --- a/WalletWasabi/Blockchain/TransactionOutputs/ICoinsView.cs +++ b/WalletWasabi/Blockchain/TransactionOutputs/ICoinsView.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using NBitcoin; using WalletWasabi.Models; @@ -37,6 +38,6 @@ public interface ICoinsView : IEnumerable ICoinsView Unspent(); - SmartCoin GetByOutPoint(OutPoint outpoint); + bool TryGetByOutPoint(OutPoint outpoint, [NotNullWhen(true)] out SmartCoin? coin); } } diff --git a/WalletWasabi/Blockchain/TransactionOutputs/SmartCoin.cs b/WalletWasabi/Blockchain/TransactionOutputs/SmartCoin.cs index bfaf8e83abc..6f45529f74d 100644 --- a/WalletWasabi/Blockchain/TransactionOutputs/SmartCoin.cs +++ b/WalletWasabi/Blockchain/TransactionOutputs/SmartCoin.cs @@ -33,7 +33,7 @@ public class SmartCoin : NotifyPropertyChangedBase, IEquatable private ISecret _secret; - private Cluster _clusters; + private Cluster _cluster; private bool _confirmed; private bool _unavailable; @@ -192,10 +192,10 @@ public ISecret Secret set => RaiseAndSetIfChanged(ref _secret, value); } - public Cluster Clusters + public Cluster Cluster { - get => _clusters; - set => RaiseAndSetIfChanged(ref _clusters, value); + get => _cluster; + set => RaiseAndSetIfChanged(ref _cluster, value); } #region DependentProperties @@ -287,7 +287,7 @@ private void Create(uint256 transactionId, uint index, Script scriptPubKey, Mone Label = SmartLabel.Merge(HdPubKey?.Label, label); - Clusters = new Cluster(this); + Cluster = new Cluster(this); SetConfirmed(); SetUnspent(); diff --git a/WalletWasabi/Blockchain/TransactionProcessing/TransactionProcessor.cs b/WalletWasabi/Blockchain/TransactionProcessing/TransactionProcessor.cs index 994fcef44f9..0261a4bff89 100644 --- a/WalletWasabi/Blockchain/TransactionProcessing/TransactionProcessor.cs +++ b/WalletWasabi/Blockchain/TransactionProcessing/TransactionProcessor.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using NBitcoin; +using WalletWasabi.Blockchain.Analysis.Clustering; using WalletWasabi.Blockchain.Keys; using WalletWasabi.Blockchain.TransactionOutputs; using WalletWasabi.Blockchain.Transactions; @@ -105,6 +106,14 @@ private ProcessedResult ProcessNoLock(SmartTransaction tx) { uint256 txId = tx.GetHash(); + // If we already have the transaction, then let's work on that. + if (TransactionStore.TryGetTransaction(txId, out var foundTx)) + { + foundTx.TryUpdate(tx); + tx = foundTx; + result = new ProcessedResult(tx); + } + // Performance ToDo: txids could be cached in a hashset here by the AllCoinsView and then the contains would be fast. if (!tx.Transaction.IsCoinBase && !Coins.AsAllCoinsView().CreatedBy(txId).Any()) // Transactions we already have and processed would be "double spends" but they shouldn't. { @@ -172,6 +181,11 @@ private ProcessedResult ProcessNoLock(SmartTransaction tx) HdPubKey foundKey = KeyManager.GetKeyForScriptPubKey(output.ScriptPubKey); if (foundKey != default) { + if (!foundKey.IsInternal) + { + tx.Label = SmartLabel.Merge(tx.Label, foundKey.Label); + } + foundKey.SetKeyState(KeyState.Used, KeyManager); if (output.Value <= DustThreshold) { @@ -216,8 +230,7 @@ private ProcessedResult ProcessNoLock(SmartTransaction tx) { if (newCoin.Height != Height.Mempool) // Update the height of this old coin we already had. { - SmartCoin oldCoin = Coins.AsAllCoinsView().GetByOutPoint(new OutPoint(txId, i)); - if (oldCoin is { }) // Just to be sure, it is a concurrent collection. + if (Coins.AsAllCoinsView().TryGetByOutPoint(new OutPoint(txId, i), out var oldCoin)) // Just to be sure, it is a concurrent collection. { result.NewlyConfirmedReceivedCoins.Add(newCoin); oldCoin.Height = newCoin.Height; diff --git a/WalletWasabi/Blockchain/Transactions/AllTransactionStore.cs b/WalletWasabi/Blockchain/Transactions/AllTransactionStore.cs index bebcffdc0d1..416edcf1685 100644 --- a/WalletWasabi/Blockchain/Transactions/AllTransactionStore.cs +++ b/WalletWasabi/Blockchain/Transactions/AllTransactionStore.cs @@ -14,22 +14,27 @@ namespace WalletWasabi.Blockchain.Transactions { public class AllTransactionStore { + public AllTransactionStore(string workFolderPath, Network network) + { + WorkFolderPath = Guard.NotNullOrEmptyOrWhitespace(nameof(workFolderPath), workFolderPath, trim: true); + IoHelpers.EnsureDirectoryExists(WorkFolderPath); + + Network = Guard.NotNull(nameof(network), network); + } + #region Initializers private string WorkFolderPath { get; set; } - private Network Network { get; set; } + private Network Network { get; } public TransactionStore MempoolStore { get; private set; } public TransactionStore ConfirmedStore { get; private set; } private object Lock { get; set; } - public async Task InitializeAsync(string workFolderPath, Network network, bool ensureBackwardsCompatibility = true) + public async Task InitializeAsync(bool ensureBackwardsCompatibility = true) { using (BenchmarkLogger.Measure()) { - WorkFolderPath = Guard.NotNullOrEmptyOrWhitespace(nameof(workFolderPath), workFolderPath, trim: true); - Network = Guard.NotNull(nameof(network), network); - MempoolStore = new TransactionStore(); ConfirmedStore = new TransactionStore(); Lock = new object(); diff --git a/WalletWasabi/Blockchain/Transactions/AllTransactionStoreMock.cs b/WalletWasabi/Blockchain/Transactions/AllTransactionStoreMock.cs index 1a559ec1f5f..c24132b0e19 100644 --- a/WalletWasabi/Blockchain/Transactions/AllTransactionStoreMock.cs +++ b/WalletWasabi/Blockchain/Transactions/AllTransactionStoreMock.cs @@ -4,6 +4,10 @@ namespace WalletWasabi.Blockchain.Transactions { public class AllTransactionStoreMock : AllTransactionStore { + public AllTransactionStoreMock(string workFolderPath, Network network) : base(workFolderPath, network) + { + } + public override bool TryGetTransaction(uint256 hash, out SmartTransaction sameStx) { sameStx = null; diff --git a/WalletWasabi/Blockchain/Transactions/TransactionFactory.cs b/WalletWasabi/Blockchain/Transactions/TransactionFactory.cs index c69c5175021..73487c26c21 100644 --- a/WalletWasabi/Blockchain/Transactions/TransactionFactory.cs +++ b/WalletWasabi/Blockchain/Transactions/TransactionFactory.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Net.Http; using System.Threading; -using System.Threading.Tasks; using WalletWasabi.Blockchain.Analysis.Clustering; using WalletWasabi.Blockchain.Keys; using WalletWasabi.Blockchain.TransactionBuilding; @@ -14,7 +13,6 @@ using WalletWasabi.Helpers; using WalletWasabi.Logging; using WalletWasabi.Models; -using WalletWasabi.Stores; using WalletWasabi.WebClients.PayJoin; namespace WalletWasabi.Blockchain.Transactions @@ -22,12 +20,12 @@ namespace WalletWasabi.Blockchain.Transactions public class TransactionFactory { /// Allow to spend unconfirmed transactions, if necessary. - public TransactionFactory(Network network, KeyManager keyManager, ICoinsView coins, BitcoinStore store, string password = "", bool allowUnconfirmed = false) + public TransactionFactory(Network network, KeyManager keyManager, ICoinsView coins, AllTransactionStore transactionStore, string password = "", bool allowUnconfirmed = false) { Network = Guard.NotNull(nameof(network), network); KeyManager = Guard.NotNull(nameof(keyManager), keyManager); Coins = Guard.NotNull(nameof(coins), coins); - Store = Guard.NotNull(nameof(store), store); + TransactionStore = Guard.NotNull(nameof(transactionStore), transactionStore); Password = password; AllowUnconfirmed = allowUnconfirmed; } @@ -35,9 +33,9 @@ public TransactionFactory(Network network, KeyManager keyManager, ICoinsView coi public Network Network { get; } public KeyManager KeyManager { get; } public ICoinsView Coins { get; } - public BitcoinStore Store { get; } public string Password { get; } public bool AllowUnconfirmed { get; } + private AllTransactionStore TransactionStore { get; } /// /// @@ -148,7 +146,7 @@ public BuildTransactionResult BuildTransaction( { KeyManager.AssertCleanKeysIndexed(isInternal: true); KeyManager.AssertLockedInternalKeysIndexed(14); - changeHdPubKey = KeyManager.GetKeys(KeyState.Clean, true).RandomElement(); + changeHdPubKey = KeyManager.GetKeys(KeyState.Clean, true).FirstOrDefault(); builder.SetChange(changeHdPubKey.P2wpkhScript); } @@ -219,7 +217,10 @@ public BuildTransactionResult BuildTransaction( Logger.LogInfo("Signing transaction..."); // It must be watch only, too, because if we have the key and also hardware wallet, we do not care we can sign. - Transaction tx = null; + psbt.AddKeyPaths(KeyManager); + psbt.AddPrevTxs(TransactionStore); + + Transaction tx; if (KeyManager.IsWatchOnly) { tx = psbt.GetGlobalTransaction(); @@ -230,16 +231,16 @@ public BuildTransactionResult BuildTransaction( builder = builder.AddKeys(signingKeys.ToArray()); builder.SignPSBT(psbt); - UpdatePSBTInfo(psbt, spentCoins, changeHdPubKey); - - if (!KeyManager.IsWatchOnly) + var isPayjoin = false; + // Try to pay using payjoin + if (payjoinClient is { }) { - // Try to pay using payjoin - if (payjoinClient is { }) - { - psbt = TryNegotiatePayjoin(payjoinClient, builder, psbt, changeHdPubKey); - } + psbt = TryNegotiatePayjoin(payjoinClient, builder, psbt, changeHdPubKey); + isPayjoin = true; + psbt.AddKeyPaths(KeyManager); + psbt.AddPrevTxs(TransactionStore); } + psbt.Finalize(); tx = psbt.ExtractTransaction(); @@ -249,13 +250,16 @@ public BuildTransactionResult BuildTransaction( throw new InvalidOperationException("Impossible to get the fee rate of the PSBT, this should never happen."); } - // Manually check the feerate, because some inaccuracy is possible. - var sb1 = feeRate.SatoshiPerByte; - var sb2 = actualFeeRate.SatoshiPerByte; - if (Math.Abs(sb1 - sb2) > 2) // 2s/b inaccuracy ok. + if (!isPayjoin) { - // So it'll generate a transactionpolicy error thrown below. - checkResults.Add(new NotEnoughFundsPolicyError("Fees different than expected")); + // Manually check the feerate, because some inaccuracy is possible. + var sb1 = feeRate.SatoshiPerByte; + var sb2 = actualFeeRate.SatoshiPerByte; + if (Math.Abs(sb1 - sb2) > 2) // 2s/b inaccuracy ok. + { + // So it'll generate a transactionpolicy error thrown below. + checkResults.Add(new NotEnoughFundsPolicyError("Fees different than expected")); + } } if (checkResults.Count > 0) { @@ -263,8 +267,6 @@ public BuildTransactionResult BuildTransaction( } } - UpdatePSBTInfo(psbt, spentCoins, changeHdPubKey); - var label = SmartLabel.Merge(payments.Requests.Select(x => x.Label).Concat(spentCoins.Select(x => x.Label))); var outerWalletOutputs = new List(); var innerWalletOutputs = new List(); @@ -307,7 +309,8 @@ public BuildTransactionResult BuildTransaction( Logger.LogInfo($"Transaction is successfully built: {tx.GetHash()}."); var sign = !KeyManager.IsWatchOnly; var spendsUnconfirmed = spentCoins.Any(c => !c.Confirmed); - return new BuildTransactionResult(new SmartTransaction(tx, Height.Unknown), psbt, spendsUnconfirmed, sign, fee, feePc, outerWalletOutputs, innerWalletOutputs, spentCoins); + SmartTransaction smartTransaction = new SmartTransaction(tx, Height.Unknown, label: SmartLabel.Merge(payments.Requests.Select(x => x.Label))); + return new BuildTransactionResult(smartTransaction, psbt, spendsUnconfirmed, sign, fee, feePc, outerWalletOutputs, innerWalletOutputs, spentCoins); } private PSBT TryNegotiatePayjoin(IPayjoinClient payjoinClient, TransactionBuilder builder, PSBT psbt, HdPubKey changeHdPubKey) @@ -344,39 +347,5 @@ private PSBT TryNegotiatePayjoin(IPayjoinClient payjoinClient, TransactionBuilde return psbt; } - - private void UpdatePSBTInfo(PSBT psbt, SmartCoin[] spentCoins, HdPubKey changeHdPubKey) - { - if (KeyManager.MasterFingerprint is HDFingerprint fp) - { - foreach (var coin in spentCoins) - { - var rootKeyPath = new RootedKeyPath(fp, coin.HdPubKey.FullKeyPath); - psbt.AddKeyPath(coin.HdPubKey.PubKey, rootKeyPath, coin.ScriptPubKey); - } - if (changeHdPubKey is { }) - { - var rootKeyPath = new RootedKeyPath(fp, changeHdPubKey.FullKeyPath); - psbt.AddKeyPath(changeHdPubKey.PubKey, rootKeyPath, changeHdPubKey.P2wpkhScript); - } - } - - foreach (var input in spentCoins) - { - var coinInputTxID = input.TransactionId; - if (Store.TransactionStore.TryGetTransaction(coinInputTxID, out var txn)) - { - var psbtInputs = psbt.Inputs.Where(x => x.PrevOut.Hash == coinInputTxID); - foreach (var psbtInput in psbtInputs) - { - psbtInput.NonWitnessUtxo = txn.Transaction; - } - } - else - { - Logger.LogWarning($"Transaction id:{coinInputTxID} is missing from the TransactionStore. Ignoring..."); - } - } - } } } diff --git a/WalletWasabi/Blockchain/Transactions/TransactionHistoryBuilder.cs b/WalletWasabi/Blockchain/Transactions/TransactionHistoryBuilder.cs index 01d9369b6d2..f5a7c0d201c 100644 --- a/WalletWasabi/Blockchain/Transactions/TransactionHistoryBuilder.cs +++ b/WalletWasabi/Blockchain/Transactions/TransactionHistoryBuilder.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; +using WalletWasabi.Blockchain.Analysis.Clustering; using WalletWasabi.Blockchain.Blocks; using WalletWasabi.Blockchain.TransactionOutputs; using WalletWasabi.Models; @@ -37,30 +38,13 @@ public List BuildHistorySummary() continue; } - DateTimeOffset dateTime; - if (foundTransaction.Height.Type == HeightType.Chain) - { - if (wallet.BitcoinStore.SmartHeaderChain.TryGetHeader((uint)foundTransaction.Height.Value, out SmartHeader header)) - { - dateTime = header.BlockTime; - } - else - { - dateTime = DateTimeOffset.UtcNow; - } - } - else - { - dateTime = foundTransaction.FirstSeen; - } - + var dateTime = foundTransaction.FirstSeen; var found = txRecordList.FirstOrDefault(x => x.TransactionId == coin.TransactionId); if (found != null) // if found then update { - var label = !string.IsNullOrEmpty(found.Label) ? found.Label + ", " : ""; - found.DateTime = dateTime; + found.DateTime = found.DateTime < dateTime ? found.DateTime : dateTime; found.Amount += coin.Amount; - found.Label = $"{label}{coin.Label}"; + found.Label = SmartLabel.Merge(found.Label, foundTransaction.Label); } else { @@ -69,7 +53,7 @@ public List BuildHistorySummary() DateTime = dateTime, Height = coin.Height, Amount = coin.Amount, - Label = coin.Label, + Label = foundTransaction.Label, TransactionId = coin.TransactionId, BlockIndex = foundTransaction.BlockIndex, IsLikelyCoinJoinOutput = coin.IsLikelyCoinJoinOutput is true @@ -83,26 +67,11 @@ public List BuildHistorySummary() throw new InvalidOperationException($"Transaction {coin.SpenderTransactionId} not found."); } - if (foundSpenderTransaction.Height.Type == HeightType.Chain) - { - if (wallet.BitcoinStore.SmartHeaderChain.TryGetHeader((uint)foundSpenderTransaction.Height.Value, out SmartHeader header)) - { - dateTime = header.BlockTime; - } - else - { - dateTime = DateTimeOffset.UtcNow; - } - } - else - { - dateTime = foundSpenderTransaction.FirstSeen; - } - + dateTime = foundSpenderTransaction.FirstSeen; var foundSpenderCoin = txRecordList.FirstOrDefault(x => x.TransactionId == coin.SpenderTransactionId); if (foundSpenderCoin != null) // if found { - foundSpenderCoin.DateTime = dateTime; + foundSpenderCoin.DateTime = foundSpenderCoin.DateTime < dateTime ? foundSpenderCoin.DateTime : dateTime; foundSpenderCoin.Amount -= coin.Amount; } else @@ -112,7 +81,7 @@ public List BuildHistorySummary() DateTime = dateTime, Height = foundSpenderTransaction.Height, Amount = Money.Zero - coin.Amount, - Label = "", + Label = foundSpenderTransaction.Label, TransactionId = coin.SpenderTransactionId, BlockIndex = foundSpenderTransaction.BlockIndex, IsLikelyCoinJoinOutput = coin.IsLikelyCoinJoinOutput is true diff --git a/WalletWasabi/Blockchain/Transactions/TransactionSummary.cs b/WalletWasabi/Blockchain/Transactions/TransactionSummary.cs index a13b7f0b471..b04e28a5c86 100644 --- a/WalletWasabi/Blockchain/Transactions/TransactionSummary.cs +++ b/WalletWasabi/Blockchain/Transactions/TransactionSummary.cs @@ -1,5 +1,6 @@ using NBitcoin; using System; +using WalletWasabi.Blockchain.Analysis.Clustering; using WalletWasabi.Models; namespace WalletWasabi.Blockchain.Transactions @@ -9,7 +10,7 @@ public class TransactionSummary public DateTimeOffset DateTime { get; set; } public Height Height { get; set; } public Money Amount { get; set; } - public string Label { get; set; } + public SmartLabel Label { get; set; } public uint256 TransactionId { get; set; } public int BlockIndex { get; set; } public bool IsLikelyCoinJoinOutput { get; set; } diff --git a/WalletWasabi/CoinJoin/Client/Clients/CoinJoinClient.cs b/WalletWasabi/CoinJoin/Client/Clients/CoinJoinClient.cs index 2c3843ae5bd..fdfb58e6aa5 100644 --- a/WalletWasabi/CoinJoin/Client/Clients/CoinJoinClient.cs +++ b/WalletWasabi/CoinJoin/Client/Clients/CoinJoinClient.cs @@ -626,6 +626,7 @@ private async Task TryRegisterCoinsAsync(ClientRound inputRegistrableRound) } } + CleanNonLockedExposedKeys(); var keysToSurelyRegister = ExposedLinks.Where(x => coinsToRegister.Contains(x.Key)).SelectMany(x => x.Value).Select(x => x.Key).ToArray(); var keysTryNotToRegister = ExposedLinks.SelectMany(x => x.Value).Select(x => x.Key).Except(keysToSurelyRegister).ToArray(); @@ -637,7 +638,7 @@ private async Task TryRegisterCoinsAsync(ClientRound inputRegistrableRound) allLockedInternalKeys = keysToSurelyRegister.Concat(allLockedInternalKeys).Distinct(); // Prefer not to bloat the wallet: - if (allLockedInternalKeys.Count() <= maximumMixingLevelCount) + if (keysTryNotToRegister.Length >= DestinationKeyManager.MinGapLimit / 2) { allLockedInternalKeys = allLockedInternalKeys.Concat(keysTryNotToRegister).Distinct(); } @@ -703,6 +704,26 @@ private async Task TryRegisterCoinsAsync(ClientRound inputRegistrableRound) return (change, actives); } + private void CleanNonLockedExposedKeys() + { + // Remove non-locked exposed keys. + foreach (var key in ExposedLinks.Keys.ToArray()) + { + if (ExposedLinks.TryGetValue(key, out var links)) + { + var lockedKeys = links.Where(x => x.Key.KeyState == KeyState.Locked).ToArray(); + if (lockedKeys.Any()) + { + ExposedLinks.AddOrReplace(key, lockedKeys); + } + else + { + ExposedLinks.TryRemove(key, out _); + } + } + } + } + public async Task QueueCoinsToMixAsync(params SmartCoin[] coins) => await QueueCoinsToMixAsync(coins as IEnumerable).ConfigureAwait(false); diff --git a/WalletWasabi/Extensions/NBitcoinExtensions.cs b/WalletWasabi/Extensions/NBitcoinExtensions.cs index 5fc3455317a..db21462a462 100644 --- a/WalletWasabi/Extensions/NBitcoinExtensions.cs +++ b/WalletWasabi/Extensions/NBitcoinExtensions.cs @@ -9,10 +9,12 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using WalletWasabi.Blockchain.Keys; using WalletWasabi.Blockchain.TransactionOutputs; using WalletWasabi.Blockchain.Transactions; using WalletWasabi.CoinJoin.Common.Crypto; using WalletWasabi.Helpers; +using WalletWasabi.Logging; using WalletWasabi.Models; using static WalletWasabi.Crypto.SchnorrBlinding; @@ -413,5 +415,63 @@ public static string ToUsdString(this Money btc, decimal usdExchangeRate, bool l { return ToCurrency(btc, "USD", usdExchangeRate, lurkingWifeMode); } + + /// + /// Tries to equip the PSBT with input and output keypaths on best effort. + /// + public static void AddKeyPaths(this PSBT psbt, KeyManager keyManager) + { + if (keyManager.MasterFingerprint.HasValue) + { + var fp = keyManager.MasterFingerprint.Value; + // Add input keypaths. + foreach (var script in psbt.Inputs.Select(x => x.WitnessUtxo?.ScriptPubKey).ToArray()) + { + if (script is { }) + { + var hdPubKey = keyManager.GetKeyForScriptPubKey(script); + if (hdPubKey is { }) + { + psbt.AddKeyPath(fp, hdPubKey, script); + } + } + } + + // Add output keypaths. + foreach (var script in psbt.Outputs.Select(x => x.ScriptPubKey).ToArray()) + { + var hdPubKey = keyManager.GetKeyForScriptPubKey(script); + if (hdPubKey is { }) + { + psbt.AddKeyPath(fp, hdPubKey, script); + } + } + } + } + + public static void AddKeyPath(this PSBT psbt, HDFingerprint fp, HdPubKey hdPubKey, Script script) + { + var rootKeyPath = new RootedKeyPath(fp, hdPubKey.FullKeyPath); + psbt.AddKeyPath(hdPubKey.PubKey, rootKeyPath, script); + } + + /// + /// Tries to equip the PSBT with previous transactions with best effort. + /// + public static void AddPrevTxs(this PSBT psbt, AllTransactionStore transactionStore) + { + // Fill out previous transactions. + foreach (var psbtInput in psbt.Inputs) + { + if (transactionStore.TryGetTransaction(psbtInput.PrevOut.Hash, out var tx)) + { + psbtInput.NonWitnessUtxo = tx.Transaction; + } + else + { + Logger.LogInfo($"Transaction id: {psbtInput.PrevOut.Hash} is missing from the {nameof(transactionStore)}. Ignoring..."); + } + } + } } } diff --git a/WalletWasabi/Helpers/Constants.cs b/WalletWasabi/Helpers/Constants.cs index 6d076e40ac1..29caed192aa 100644 --- a/WalletWasabi/Helpers/Constants.cs +++ b/WalletWasabi/Helpers/Constants.cs @@ -56,9 +56,9 @@ public static class Constants public const long MaxSatoshisSupply = 2_100_000_000_000_000L; - public static readonly Version ClientVersion = new Version(1, 1, 12, 0); - public static readonly Version HwiVersion = new Version("1.1.2"); - public static readonly Version BitcoinCoreVersion = new Version("0.20.0"); + public static readonly Version ClientVersion = new Version(1, 1, 13, 0); + public static readonly Version HwiVersion = new Version("2.0.2"); + public static readonly Version BitcoinCoreVersion = new Version("0.21.1"); public static readonly Version LegalDocumentsVersion = new Version(2, 0); public static readonly NodeRequirement NodeRequirements = new NodeRequirement diff --git a/WalletWasabi/Helpers/IoHelpers.cs b/WalletWasabi/Helpers/IoHelpers.cs index 6ab04dcd6bf..30e45292e2a 100644 --- a/WalletWasabi/Helpers/IoHelpers.cs +++ b/WalletWasabi/Helpers/IoHelpers.cs @@ -97,15 +97,14 @@ public static void EnsureFileExists(string filePath) } } - public static byte[] GetHashFile(string filePath) + public static byte[] GetHashFile(byte[] bytes) { - var bytes = File.ReadAllBytes(filePath); return HashHelpers.GenerateSha256Hash(bytes); } - public static bool CheckExpectedHash(string filePath, string sourceFolderPath) + public static bool CheckExpectedHash(byte[] bytes, string sourceFolderPath) { - var fileHash = GetHashFile(filePath); + var fileHash = GetHashFile(bytes); try { var digests = File.ReadAllLines(Path.Combine(sourceFolderPath, "digests.txt")); diff --git a/WalletWasabi/Http/Models/HeaderSection.cs b/WalletWasabi/Http/Models/HeaderSection.cs index 61339ddde54..f1f9b0f5526 100644 --- a/WalletWasabi/Http/Models/HeaderSection.cs +++ b/WalletWasabi/Http/Models/HeaderSection.cs @@ -69,10 +69,10 @@ private static void ValidateAndCorrectHeaders(HeaderSection hs) foreach (var f in hs.Fields) { // if we find host - if (f.Name == "Host") + if (f.Name.Equals("Host", StringComparison.OrdinalIgnoreCase)) { // if host is not first - if (hs.Fields.First().Name != "Host") + if (!hs.Fields.First().Name.Equals("Host", StringComparison.OrdinalIgnoreCase)) { // then correct host hostToCorrect = f; @@ -102,7 +102,7 @@ private static void ValidateAndCorrectHeaders(HeaderSection hs) var allParts = new HashSet(); foreach (var field in hs.Fields) { - if (field.Name == "Content-Length") + if (field.Name.Equals("Content-Length", StringComparison.OrdinalIgnoreCase)) { var parts = field.Value.Trim().Split(','); foreach (var part in parts) @@ -117,7 +117,7 @@ private static void ValidateAndCorrectHeaders(HeaderSection hs) { throw new InvalidDataException("Invalid Content-Length."); } - hs.Fields.RemoveAll(x => x.Name == "Content-Length"); + hs.Fields.RemoveAll(x => x.Name.Equals("Content-Length", StringComparison.OrdinalIgnoreCase)); hs.Fields.Add(new HeaderField("Content-Length", allParts.First())); } } @@ -131,7 +131,7 @@ public HttpRequestContentHeaders ToHttpRequestHeaders() message.Content.Headers.ContentLength = null; foreach (var field in Fields) { - if (field.Name.StartsWith("Content-", StringComparison.Ordinal)) + if (field.Name.StartsWith("Content-", StringComparison.OrdinalIgnoreCase)) { message.Content.Headers.TryAddWithoutValidation(field.Name, field.Value); } @@ -157,7 +157,7 @@ public HttpResponseContentHeaders ToHttpResponseHeaders() message.Content.Headers.ContentLength = null; foreach (var field in Fields) { - if (field.Name.StartsWith("Content-", StringComparison.Ordinal)) + if (field.Name.StartsWith("Content-", StringComparison.OrdinalIgnoreCase)) { message.Content.Headers.TryAddWithoutValidation(field.Name, field.Value); } @@ -191,7 +191,7 @@ public static HeaderSection CreateNew(HttpHeaders headers) { if (contentHeaders.ContentLength != null) { - if (hs.Fields.All(x => x.Name != "Content-Length")) + if (hs.Fields.All(x => !x.Name.Equals("Content-Length", StringComparison.OrdinalIgnoreCase))) { hs.Fields.Add(new HeaderField("Content-Length", contentHeaders.ContentLength.ToString())); } diff --git a/WalletWasabi/Hwi/HwiClient.cs b/WalletWasabi/Hwi/HwiClient.cs index c3feebf92a4..95c1cd609fe 100644 --- a/WalletWasabi/Hwi/HwiClient.cs +++ b/WalletWasabi/Hwi/HwiClient.cs @@ -162,7 +162,7 @@ private async Task DisplayAddressImplAsync(HardwareWall var response = await SendCommandAsync( options: BuildOptions(deviceType, devicePath, fingerprint), command: HwiCommands.DisplayAddress, - commandArguments: $"--path {keyPath.ToString(true, "h")} --wpkh", + commandArguments: $"--path {keyPath.ToString(true, "h")} --addr-type wit", openConsole: false, cancel).ConfigureAwait(false); diff --git a/WalletWasabi/Hwi/Models/HardwareWalletModels.cs b/WalletWasabi/Hwi/Models/HardwareWalletModels.cs index c632838d2b1..bdf5c0019fe 100644 --- a/WalletWasabi/Hwi/Models/HardwareWalletModels.cs +++ b/WalletWasabi/Hwi/Models/HardwareWalletModels.cs @@ -17,9 +17,12 @@ public enum HardwareWalletModels KeepKey, KeepKey_Simulator, Ledger_Nano_S, + Ledger_Nano_X, Trezor_1, Trezor_1_Simulator, Trezor_T, - Trezor_T_Simulator + Trezor_T_Simulator, + BitBox02_BTCOnly, + BitBox02_Multi, } } diff --git a/WalletWasabi/Hwi/Models/HwiCommands.cs b/WalletWasabi/Hwi/Models/HwiCommands.cs index 506dab2bb85..8dacf0916ba 100644 --- a/WalletWasabi/Hwi/Models/HwiCommands.cs +++ b/WalletWasabi/Hwi/Models/HwiCommands.cs @@ -60,7 +60,7 @@ public enum HwiCommands /// /// Notable arguments: /// - /// --path - Derivation path, default follows BIP43 convention, e.g. m/84h/0h/0h/1/* with --wpkh --internal. If this argument and --internal is not given, both internal and external keypools will be returned. + /// --path - Derivation path, default follows BIP43 convention, e.g. m/84h/0h/0h/1/* with --addr-type wit --internal. If this argument and --internal is not given, both internal and external keypools will be returned. /// start - The index to start at. /// end - The index to end at. /// diff --git a/WalletWasabi/Hwi/Parsers/HwiParser.cs b/WalletWasabi/Hwi/Parsers/HwiParser.cs index 47fd9365aa0..cc1cffa1002 100644 --- a/WalletWasabi/Hwi/Parsers/HwiParser.cs +++ b/WalletWasabi/Hwi/Parsers/HwiParser.cs @@ -342,19 +342,14 @@ public static string ToArgumentString(Network network, IEnumerable op var optionsString = string.Join(" --", fullOptions.Select(x => { - string optionString; - if (x.Type == HwiOptions.DeviceType) + string optionString = x.Type switch { - optionString = "device-type"; - } - else if (x.Type == HwiOptions.DevicePath) - { - optionString = "device-path"; - } - else - { - optionString = x.Type.ToString().ToLowerInvariant(); - } + HwiOptions.DeviceType => "device-type", + HwiOptions.DevicePath => "device-path", + HwiOptions.TestNet => "chain test", + _ => x.Type.ToString().ToLowerInvariant(), + }; + if (string.IsNullOrWhiteSpace(x.Arguments)) { return optionString; diff --git a/WalletWasabi/Legal/Assets/LegalDocuments.txt b/WalletWasabi/Legal/Assets/LegalDocuments.txt index 79cfc5d3b81..a40a4dbe6d1 100644 --- a/WalletWasabi/Legal/Assets/LegalDocuments.txt +++ b/WalletWasabi/Legal/Assets/LegalDocuments.txt @@ -170,7 +170,6 @@ I. TERMS AND CONDITIONS Any failure or delay by us to exercise or enforce any right or remedy provided under these Terms or by law will not constitute a waiver of that or any other right or remedy, nor will it preclude any further exercise of that or any other right or remedy. No single or partial right exercise of any right or remedy shall preclude or restrict the further exercise of that or any other right or remedy. 9.5 ASSIGNMENT - The Service Provider may assign these Terms to its parent company, affiliate or subsidiary, or in connection with a merger, consolidation, or sale or other disposition of all or substantially all of its assets. You may not assign these Terms or Your use of or access to the Services at any time. 9.6 ENTIRE AGREEMENT diff --git a/WalletWasabi/Microservices/Binaries/lin64/bitcoind b/WalletWasabi/Microservices/Binaries/lin64/bitcoind index decef8ebffd..89fe1c726b0 100755 Binary files a/WalletWasabi/Microservices/Binaries/lin64/bitcoind and b/WalletWasabi/Microservices/Binaries/lin64/bitcoind differ diff --git a/WalletWasabi/Microservices/Binaries/lin64/hwi b/WalletWasabi/Microservices/Binaries/lin64/hwi index b785c39bc2f..d07f1229f24 100755 Binary files a/WalletWasabi/Microservices/Binaries/lin64/hwi and b/WalletWasabi/Microservices/Binaries/lin64/hwi differ diff --git a/WalletWasabi/Microservices/Binaries/osx64/bitcoind b/WalletWasabi/Microservices/Binaries/osx64/bitcoind index 31aa88cf095..2e0407bba9f 100755 Binary files a/WalletWasabi/Microservices/Binaries/osx64/bitcoind and b/WalletWasabi/Microservices/Binaries/osx64/bitcoind differ diff --git a/WalletWasabi/Microservices/Binaries/osx64/hwi b/WalletWasabi/Microservices/Binaries/osx64/hwi index 24852e9f52b..b463ede692b 100755 Binary files a/WalletWasabi/Microservices/Binaries/osx64/hwi and b/WalletWasabi/Microservices/Binaries/osx64/hwi differ diff --git a/WalletWasabi/Microservices/Binaries/win64/bitcoind.exe b/WalletWasabi/Microservices/Binaries/win64/bitcoind.exe old mode 100644 new mode 100755 index 49b49880221..095f248fc7f Binary files a/WalletWasabi/Microservices/Binaries/win64/bitcoind.exe and b/WalletWasabi/Microservices/Binaries/win64/bitcoind.exe differ diff --git a/WalletWasabi/Microservices/Binaries/win64/hwi.exe b/WalletWasabi/Microservices/Binaries/win64/hwi.exe index 65648fd27d6..2afc01600c9 100755 Binary files a/WalletWasabi/Microservices/Binaries/win64/hwi.exe and b/WalletWasabi/Microservices/Binaries/win64/hwi.exe differ diff --git a/WalletWasabi/Microservices/MicroserviceHelpers.cs b/WalletWasabi/Microservices/MicroserviceHelpers.cs index bfbad2c049d..bb608495b0f 100644 --- a/WalletWasabi/Microservices/MicroserviceHelpers.cs +++ b/WalletWasabi/Microservices/MicroserviceHelpers.cs @@ -1,31 +1,51 @@ using System; -using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; -using System.Text; using WalletWasabi.Helpers; namespace WalletWasabi.Microservices { public static class MicroserviceHelpers { - public static string GetBinaryPath(string binaryNameWithoutExtension) + public static OSPlatform GetCurrentPlatform() { - var fullBaseDirectory = EnvironmentHelpers.GetFullBaseDirectory(); - - string commonPartialPath = Path.Combine(fullBaseDirectory, "Microservices", "Binaries"); - string path; if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - path = Path.Combine(commonPartialPath, $"win64", $"{binaryNameWithoutExtension}.exe"); + return OSPlatform.Windows; } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { - path = Path.Combine(commonPartialPath, $"lin64", binaryNameWithoutExtension); + return OSPlatform.Linux; } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { - path = Path.Combine(commonPartialPath, $"osx64", binaryNameWithoutExtension); + return OSPlatform.OSX; + } + else + { + throw new NotSupportedException("Platform is not supported."); + } + } + + public static string GetBinaryFolder(OSPlatform? platform = null) + { + platform ??= GetCurrentPlatform(); + + string fullBaseDirectory = EnvironmentHelpers.GetFullBaseDirectory(); + string commonPartialPath = Path.Combine(fullBaseDirectory, "Microservices", "Binaries"); + + string path; + if (platform == OSPlatform.Windows) + { + path = Path.Combine(commonPartialPath, "win64"); + } + else if (platform == OSPlatform.Linux) + { + path = Path.Combine(commonPartialPath, "lin64"); + } + else if (platform == OSPlatform.OSX) + { + path = Path.Combine(commonPartialPath, "osx64"); } else { @@ -34,5 +54,14 @@ public static string GetBinaryPath(string binaryNameWithoutExtension) return path; } + + public static string GetBinaryPath(string binaryNameWithoutExtension, OSPlatform? platform = null) + { + platform ??= GetCurrentPlatform(); + string binaryFolder = GetBinaryFolder(platform); + string fileName = platform.Value == OSPlatform.Windows ? $"{binaryNameWithoutExtension}.exe" : $"{binaryNameWithoutExtension}"; + + return Path.Combine(binaryFolder, fileName); + } } } diff --git a/WalletWasabi/OnionSeeds/MainOnionSeeds.txt b/WalletWasabi/OnionSeeds/MainOnionSeeds.txt deleted file mode 100644 index 8e2086114d9..00000000000 --- a/WalletWasabi/OnionSeeds/MainOnionSeeds.txt +++ /dev/null @@ -1,642 +0,0 @@ -2253ceablq3kxxqc.onion:8333 -22avjqm5gdejvkny.onion:8333 -22h7b6f3caabqqsu.onion:8333 -23wdfqkzttmenvki.onion:8333 -24ehe7fzpif6socn.onion:8333 -252zundaimb2yr3u.onion:8333 -26suaasojyjmao5n.onion:8333 -26ywgrbwihncu2ie.onion:8333 -2ckmbf6sglwydeth.onion:8333 -2hkusi5gcaautwqf.onion:8333 -2kdpulf67furyiyb.onion:8333 -2najg4gtiypdj42q.onion:8333 -2nuqptros6moc24g.onion:8333 -2ocixiqlvy4qz2cn.onion:8333 -2p35wtw7ce3xixcs.onion:8333 -2qudbhlnvqpli3sz.onion:8333 -2ujxdfovfyjpmdto.onion:8333 -2vmdtfcazhpyvwez.onion:8333 -2x3awpeslq3hd5pm.onion:8333 -2xdzsruhsej4tsiw.onion:8333 -2y42fx6hbvl4b4zr.onion:8333 -2yltplyrio7risk6.onion:8333 -2zndatxfidey7ccl.onion:8333 -336lqgffb4tg5gpm.onion:8333 -34dh2km3ed53yvc3.onion:8333 -34hmce4dxd7yt22o.onion:8333 -34ran2woq4easmss.onion:8333 -3axtxivtai6cpbib.onion:8333 -3b7pgjgxng5rqmj6.onion:8333 -3eokyju7zzizga6r.onion:8333 -3eug25qadzlxj3w7.onion:8333 -3f3xawn7xkr4ggzv.onion:8333 -3f4wuusjz6vdvx6b.onion:8333 -3fnspyokv2aargef.onion:8333 -3jjjtbexyoqqhnli.onion:8333 -3kmqfipkt324fukl.onion:8333 -3mbgbuz2ffidfc5u.onion:8333 -3r3mocmw4jzxq4xk.onion:8333 -3r44ddzjitznyahw.onion:8333 -3reyvetpqrg55gzw.onion:8333 -3rjc5djyqvtz6kzw.onion:8333 -3s223cqau4rif7dk.onion:8333 -3wauhhcdlqkz767z.onion:8333 -47uupgzcnrwahoto.onion:8333 -4a5uxm3prnx5meyh.onion:8333 -4bbgau4eaczmic6m.onion:8333 -4bwmsugx2pevsepr.onion:8333 -4ekwny4rymulkmln.onion:8333 -4jxz37oou5ag763c.onion:8333 -4nnuyxm5k5tlyjq3.onion:8333 -4nz2yg4cnote3ej7.onion:8333 -4ofhtl2uojso6fhg.onion:8333 -4ru2je6gep2illmh.onion:8333 -4u2p24d227y5uuzf.onion:8333 -4uam4wd5afzzabkv.onion:8333 -52unuecowvoiyyx4.onion:8333 -54ck6txobyzhtsup.onion:8333 -55j2lvmbc7bz6piw.onion:8333 -55n2vc44uqfkbtox.onion:8333 -55owdmjvryxpw2lc.onion:8333 -57acrug2miefbr2r.onion:8333 -5c56vtn2e542mjec.onion:8333 -5d5vtnm6xlsqzq7p.onion:8333 -5eaucsyzx5xh4ysh.onion:8333 -5eazyuyrcfxo2zqs.onion:8333 -5ebyajd3vyouxkiq.onion:8333 -5hux3p66qguigg7g.onion:8333 -5ityqoefmhlqhjgr.onion:8333 -5jyfzhwksb6urrp2.onion:8333 -5nsfm4nqqzzprjrp.onion:8333 -5oww42hauybusfi6.onion:8333 -5pzt6uzgvxu6g75v.onion:8333 -5qdxswcgigxilqin.onion:8333 -5s5jgoyba736gq3k.onion:8333 -5wfp4dsolxlg2466.onion:8333 -64arj7kivj6lq5gu.onion:8333 -66nfktjangs57ki2.onion:8333 -66wfuvvks3d2k3au.onion:8333 -6btuta4rk446o5j5.onion:8333 -6ca2yzsqflckc65n.onion:8333 -6hm7ygxq6v4egu57.onion:8333 -6jlqyblit3l7x4jt.onion:8333 -6jxlplxeanxp4x5p.onion:8333 -6kc3lldy6fjmdvvr.onion:8333 -6le7yllw2p3oza4l.onion:8333 -6luc7owlbbaj52lr.onion:8333 -6ouvvc7vgcdsaepv.onion:8333 -6qbclq3xeg3dssox.onion:8333 -6rathipvjx5mi3jc.onion:8333 -6ressv4dvplb5ihh.onion:8333 -6rlgg2n32qkyeq6d.onion:8333 -6sslsvxw7prz6bon.onion:8333 -6tqpgd7bxi2ezhwh.onion:8333 -6xqy4ts6bo6u5dgm.onion:8333 -6zgkx7cv5x5beamw.onion:8333 -6zh3vsdtaskr5acq.onion:8333 -74akej7m5t2rw7mg.onion:8333 -75o5gxjqcqejvnly.onion:8333 -7aqnfz2vvzygjbcx.onion:8333 -7bb6zs3mxhkucism.onion:8333 -7bnhohngpol3m5in.onion:8333 -7boldn2p5n4xjiop.onion:8333 -7ena27ppvyt7vdmw.onion:8333 -7gzx4ieoxvez35oc.onion:8333 -7iljxkcuykternrt.onion:8333 -7mmlsrip2irzeq2l.onion:8333 -7ndbwnmgbyupv47f.onion:8333 -7nfkxw5ejk4j4auy.onion:8333 -7njp3eitf7cmdnvn.onion:8333 -7nokb2y2ei2zf2pd.onion:8333 -7p5mi7xj67a2qqu3.onion:8333 -7qobltg5nz65veav.onion:8333 -7rjgknhabdumwxhg.onion:8333 -7vodtqbi7uyrbp77.onion:8333 -7vv2ozyjjnzrmtyn.onion:8333 -7x7yedi4j3udqeld.onion:8333 -7xt7wwioayyc6rpq.onion:8333 -a27bvhina4y23jxo.onion:8333 -a2njskvuxszu66km.onion:8333 -a4ln2cwzwdnazkkn.onion:8333 -a7bqdujuonob6k5f.onion:8333 -actjgejqz463w23r.onion:8333 -acyizvrqiqg2qalu.onion:8333 -aed2mp6isgbfzunr.onion:8333 -aefx7ubzpal7clak.onion:8333 -ageil6vjp4ve5v6n.onion:8333 -aiej74y73obtgjj4.onion:8333 -al2wvsh2tg4e3tlr.onion:8333 -al35fqhuabzkbxfm.onion:8333 -am2b3deh4okdpsyb.onion:8333 -annqtu6rk2r6p533.onion:8333 -aquer2jk3y3sk6qg.onion:8333 -arcofqfw47petltq.onion:8333 -as67f75x4adk3a2b.onion:8333 -avn4hevnwomzme3d.onion:8333 -awmdz2fs3b5h5ut5.onion:8333 -axylx2btbfbhbhmb.onion:8333 -ayokhx2oarxg36ky.onion:8333 -azlvnosmumkfday4.onion:8333 -b3nqexycjxfbbxrt.onion:8333 -b4ilebyxcu6nttio.onion:8333 -ba3r2kl7jvqw2r32.onion:8333 -bc7i62e65vkge3tv.onion:8333 -bddfqxps5ibd3ftw.onion:8333 -bdkqtnjhjtjmvdch.onion:8333 -bgij6664uyhsbjkp.onion:8333 -bhaejs2yv6klpkeu.onion:8333 -bitcoin4rlfa4wqx.onion:8333 -bitcoinfc2fehgbn.onion:8333 -bitcoinostk4e4re.onion:8333 -bjxgfe2cdq63dcng.onion:8333 -bk7yp6epnmcllq72.onion:8333 -blc5jji5njjmloic.onion:8333 -bmq2x2lhbosdfugt.onion:8333 -bodhyiwifcypuetk.onion:8333 -bpq5d2qoocwntg3l.onion:8333 -bq2xx4mviidnbonw.onion:8333 -bqdeaacsmmosx34k.onion:8333 -bqkicjizkkk763sv.onion:8333 -bsrtufzzl3zwhjrr.onion:8333 -btcxau4tfbp2rx5h.onion:8333 -btq3twhppgrsondt.onion:8333 -byihlltnfrgmjhtf.onion:8333 -bz5b5il57fk4bt6o.onion:8333 -bzxdulahtusjh7si.onion:8333 -c33vxulv54qqtuxm.onion:8333 -c3e26duumllwnk6d.onion:8333 -c5yshk3nqyimjypv.onion:8333 -c6zc5ynvkrufezge.onion:8333 -c74ng3lrfjc4hdqb.onion:8333 -cfjdyen6uomqx6ln.onion:8333 -cha56pwbwfk2yzbv.onion:8333 -chiphuvuwoietiye.onion:8333 -ckekp7pf52wmtw4k.onion:8333 -clpdudfcazpgn5ke.onion:8333 -clyhpjgvvk5tjjye.onion:8333 -cncwik3tnd2ejm5z.onion:8333 -cqwcyvvk5xnqv3yw.onion:8333 -cyp354fpr3vdxhsy.onion:8333 -czp7wgaus4gvio72.onion:8333 -d2etoihff5ybbkwp.onion:8333 -d2mabm6bhz5th4hy.onion:8333 -d2sk45u6ca64yeqh.onion:8333 -d4si742mmi3tem26.onion:8333 -d6fan7afc3dc7lxi.onion:8333 -d6tv4svevzdcsy3a.onion:8333 -d6zbw2sxnxgj5sv3.onion:8333 -darov26as3x32a7z.onion:8333 -db5rd5e46t7mgini.onion:8333 -db63gah7hxfjo3t2.onion:8333 -dcgmq654i5m2syxc.onion:8333 -dctucgjnjigfgvkj.onion:8333 -ddivo3bxxmaweb3q.onion:8333 -ddpth2mwt3rsvoog.onion:8333 -ddxdqvsyv6m3hkvh.onion:8333 -dfpl3zygpwlq6gar.onion:8333 -dhpojudhkv6vmffi.onion:8333 -dijjhnveaolpnqv5.onion:8333 -dmudsr7x7edvyglt.onion:8333 -dp2gltnp6ww72kzk.onion:8333 -duooqafocrj7ghh3.onion:8333 -duqdliptc22i6hf5.onion:8333 -dxtorauxe4mxja72.onion:8333 -dxzjzif3bnw3ibpm.onion:8333 -eagjeugoowpl73dk.onion:8333 -eaxjuhvexjomn3pv.onion:8333 -ed3wojdfya73ai2x.onion:8333 -ejljqpv2hjcef7bd.onion:8333 -enhj7uzydv6ukagp.onion:8333 -erc6tjs2ucyadl23.onion:8333 -esigoqa2je2gh5fv.onion:8333 -eue2n5sk5tktg5bv.onion:8333 -evjuy6sciqvfulkv.onion:8333 -f324ue2jcpayp57t.onion:8333 -f3c4ly43lmvmst5b.onion:8333 -fbmfgh2n7pzc4ns7.onion:8333 -fdk7by74nrwg2pkp.onion:8333 -fevgzggjvqcf2lcv.onion:8333 -fhwv7i7qpnvyz4fa.onion:8333 -fl6pilphutg4h5dp.onion:8333 -fle7rghsnfa52zej.onion:8333 -fmlmt4kkamz36coo.onion:8333 -fnghy52jg3igh3ir.onion:8333 -fqunuhlwvd7rq6d5.onion:8333 -frfelnb3vb5v2hgb.onion:8333 -fsdll7vm5vlm2y4z.onion:8333 -fuwzbfn44irqmmxy.onion:8333 -fyjbbn3jpazm7ycv.onion:8333 -g3vlnaaaog5sgui5.onion:8333 -g44i6jwsutkwmspz.onion:8333 -g55t65d5ckjixcnw.onion:8333 -g7sitavtm7hq2syi.onion:8333 -gbr37bhjswme7o2o.onion:8333 -gddgd6s562x4dndd.onion:8333 -gf42tod7n3zhgxun.onion:8333 -gfvnnnwcddfzosav.onion:8333 -ggvnc3v5pmrlsupw.onion:8333 -gk6djfhw4mmyuk6y.onion:8333 -gkzjfactlf7bh3ts.onion:8333 -gl67vhmlffhgvmln.onion:8333 -glowkhlhakhcobte.onion:8333 -glydcpeosnm6b4hk.onion:8333 -gm2p7dseb67iits3.onion:8333 -golevvyaydsduuw2.onion:8333 -gripl5xjwy2dcr6c.onion:8333 -gthhzlmqci22nxru.onion:8333 -gucdnlwh3p6qmwq5.onion:8333 -gvb6rwpg7egeda2j.onion:8333 -gwykxn4vtpz7piem.onion:8333 -gytjtps645sy3zbu.onion:8333 -gz6kazq27kmsu4qe.onion:8333 -h5orakrzfrl3vwr2.onion:8333 -h72atceevek3hjmq.onion:8333 -h7kzsvjyh55rim7f.onion:8333 -hafwtrbooszoembm.onion:8333 -hcv6foxh5mk7fhb5.onion:8333 -hgyrxtz2fq4hfuds.onion:8333 -hiymygt6mrxjih6y.onion:8333 -hjcpxikrd72ukl6p.onion:8333 -hk54vjhf7ajdcphw.onion:8333 -hk5picuh5cu5bxcn.onion:8333 -hkrjljdircbyhgtd.onion:8333 -hl7ab3tekhtpefth.onion:8333 -hod5ee37dx2srnh5.onion:8333 -hqwvjpcb5u3p6qgc.onion:8333 -hrqmrt6lsdqc7ino.onion:8333 -hscippmrclvientf.onion:8333 -hu3wvv2xp6vn3pzf.onion:8333 -hu64s2mdr3x7yxka.onion:8333 -hwwdpk24b2qgslz6.onion:8333 -hyipxou2glnor6ug.onion:8333 -i3a5xtzfm4xwtybd.onion:8333 -i3g2srm72m2mdkrd.onion:8333 -icfgs3fctckd4yeo.onion:8333 -ieghlrdtlofd4tos.onion:8333 -if32zo5u4mhdunfd.onion:8333 -ihfgsiuulcnbuzy2.onion:8333 -ihhcr7fhczqdac4y.onion:8333 -ij5gf7g456hie6i4.onion:8333 -ij5zwouvxtaxjqli.onion:8333 -iksxni6iflgdortg.onion:8333 -ilrmrgxa7kxn4nao.onion:8333 -imfag5h6lrkxryoy.onion:8333 -imfummc3ck2rabrr.onion:8333 -imwdgkthqetjgiyp.onion:8333 -invee7mnb3rvleu6.onion:8333 -ip3puuqghumfz5ww.onion:8333 -ipgbok7tcx7zc4m2.onion:8333 -iqe7o7k2f74vdtbk.onion:8333 -iqfsmzg7isrln3b7.onion:8333 -iugw42ih6hprqr26.onion:8333 -iusfiojgbyxhluk2.onion:8333 -ivm7ga5yic23pb3g.onion:8333 -ivsxdwku5og2zj4l.onion:8333 -j5e2yuan57v2h5el.onion:8333 -j5lnbf2pesm4ygnn.onion:8333 -jbxhxnygl6popvzv.onion:8333 -jc6jmf7f64iyggf4.onion:8333 -jceptnicyeukw64j.onion:8333 -jd2rpyp6nvcf2nkw.onion:8333 -jglskob2kwcugbm5.onion:8333 -jhqekvz5iq3urxyk.onion:8333 -jiuuuislm7ooesic.onion:8333 -jj55yazo3xh4y2k5.onion:8333 -jm2chnew47pnn5h5.onion:8333 -jmejrhhwatw7hdup.onion:8333 -jqkfqcjxhhh6kv45.onion:8333 -jqsx6ag4hujpapxq.onion:8333 -js5qbirosykw42jg.onion:8333 -jsk4kk4pkpdu7otn.onion:8333 -jsqbday3e4tqmlrx.onion:8333 -jtksnokusbzms7wl.onion:8333 -jvyti3egidqb6gb5.onion:8333 -jxc6ay3qwlpvxrg2.onion:8333 -jyn6oh2w3fsbxawz.onion:8333 -jze6ukn4idrh44eo.onion:8333 -jzvmidh23z7ltxfs.onion:8333 -k5wsqdamev7wftkf.onion:8333 -k6gwqzotx3drcs7t.onion:8333 -k7cc6s4eus2lzne3.onion:8333 -ka47ld4bkxryumap.onion:8333 -kaba77o5djun43qp.onion:8333 -kb4ldeakn43ypx3v.onion:8333 -kefa2heluahzgiy3.onion:8333 -kf23clvjp3p3myfe.onion:8333 -kfncsfqunua6uxyv.onion:8333 -kfqil44pdsjbncqa.onion:8333 -kgmpoz7kuthhzvcc.onion:8333 -kgplixoaos3jcl2l.onion:8333 -khnv3hpswpojun2m.onion:8333 -kkdas3qebkosygu5.onion:8333 -kl23ofag3ukb6hxl.onion:8333 -klhij4ear5x7wrcf.onion:8333 -ko37ti7twplktxqu.onion:8333 -kohb3oe36oohixsc.onion:8333 -ktjngeiuui6tlbbc.onion:8333 -kulz47yca7ybyfvg.onion:8333 -kz3oxg7745dxt62q.onion:8333 -kzu26bpw35y3pni5.onion:8333 -l44bisuxhh7reb5q.onion:8333 -l44rp6sko3zio6lr.onion:8333 -l47ck26gwmxv4ben.onion:8333 -l5ewq7rhl27ild4r.onion:8333 -l5tviyqrcofpz3wx.onion:8333 -l5ygiu327366j5sm.onion:8333 -l6vfl6zepydtoygw.onion:8333 -l7sloscjqqbifcsw.onion:8333 -laafjqvtog7djfl2.onion:8333 -lbgsqbh3nxtweapk.onion:8333 -lbq2a7pnpmviw2qo.onion:8333 -lerhgkwxsyn3kbov.onion:8333 -lfbaqxgzu7gmwpz3.onion:8333 -lgkvbvro67jomosw.onion:8333 -li4hl4b7ouhgfl5b.onion:8333 -lj7vtaurz3datses.onion:8333 -ljs7gwrmmza6q6ga.onion:8333 -lp3slo7qpg2fbtg6.onion:8333 -lqyemaprsqsnbg5w.onion:8333 -lu737rcxjaldg4za.onion:8333 -lvqv5nehy7q472kl.onion:8333 -lygy4i7brs2xczid.onion:8333 -lz2zlnmyynwtgwf2.onion:8333 -lz34apciizwaeldj.onion:8333 -lznychjqoq6fzhqa.onion:8333 -m2w5tladxs6rblb2.onion:8333 -m6hu323uqurhsbus.onion:8333 -m6qolf2yhoe3damo.onion:8333 -mdte2do4t5pehjif.onion:8333 -merrtgoxq67be4bt.onion:8333 -mfidj2jfj4ply3os.onion:8333 -mgpkinvnou42qzz4.onion:8333 -mhfksqofli366q6d.onion:8333 -mlb45gy4r7cebttc.onion:8333 -mnaeg3bw4ob5amp3.onion:8333 -motoixfjxnf4joga.onion:8333 -mpvet7ad67x6ubkf.onion:8333 -msb4dzkqpnuu5m5o.onion:8333 -mszbgym2f5rjj5aw.onion:8333 -mtixjutrm226btx3.onion:8333 -mua4plzub2rkno2p.onion:8333 -mwg3wapk3trgyg3e.onion:8333 -n3nye2jvnwuksobc.onion:8333 -n4affo546ywqalmc.onion:8333 -n6d46vbzx43bevlb.onion:8333 -n6t6kfgzlvozxhfm.onion:8333 -n7izcilafiyqwmo5.onion:8333 -ndy34ru7felunyqo.onion:8333 -nejai3vg5weze2qf.onion:8333 -nesxfmano25clfvn.onion:8333 -nf2ps7vrscyxa67f.onion:8333 -ngfawlkrw3jjc5ms.onion:8333 -niotpbuumh3sicho.onion:8333 -nkvhwxbroaphjewp.onion:8333 -nkvlns7kcaseyazi.onion:8333 -nlyjmpcmpaz5b4aa.onion:8333 -nnmv7z65k65mcesr.onion:8333 -np2uohnofascnlvy.onion:8333 -nqmxpgrpuysullkq.onion:8333 -nrm6jc4joja7i7je.onion:8333 -nrrmkgmulpgsbwlt.onion:8333 -ntv32jceqelbxysh.onion:8333 -nwjovzvu3vmvx4ab.onion:8333 -nwky3wd3ihoidvb5.onion:8333 -nwnik2ibigscln7t.onion:8333 -o2gumvbkw6pm45cf.onion:8333 -o2kgwqbblmbkbjmj.onion:8333 -o6kjqxbae7jh26so.onion:8333 -o6uadyeqokzrykid.onion:8333 -o74dazf225r22ocq.onion:8333 -oah44d75fd3sbyf3.onion:8333 -odvphjzr4r3kejm3.onion:8333 -oeemfjp25ocy6eui.onion:8333 -oevilcp2wsl5vcb4.onion:8333 -ofavhvww467evngp.onion:8333 -oigu7vi5u5n24qmr.onion:8333 -oinhdhizis27mm5b.onion:8333 -ojdock5ng3qjjb3z.onion:8333 -ojjf6x5okqne7q4m.onion:8333 -okzonmwyclkqihdw.onion:8333 -olshhn67tbt5clfs.onion:8333 -oqoia65oxmn3v24f.onion:8333 -oqw3mfoiobqcklxh.onion:8333 -osvtnellmbsnjk72.onion:8333 -osxojguiq3fm3ubo.onion:8333 -oteage2il5onylty.onion:8333 -otrpc47ftmzwu3l5.onion:8333 -ou6kteqi77gu6wwl.onion:8333 -ox4j6wbi334ut4qg.onion:8333 -oy7ss3hm2okx4tun.onion:8333 -oyzpcchcsvbf5lmr.onion:8333 -ozyt2puw2ndupedb.onion:8333 -p2pc6wbaepvdi6ce.onion:8333 -p77nyzeolhwhsspz.onion:8333 -p7h5qq26evoynjaw.onion:8333 -pak3osogyp43ktdn.onion:8333 -paot7erqftbiyb5o.onion:8333 -paygnnxbyu6t5exz.onion:8333 -pcmqhhpxqebfprm7.onion:8333 -ppnyco2ganvru2pk.onion:8333 -psfg2x4is34bt2j7.onion:8333 -ptbwqhusps5qieql.onion:8333 -ptescnygpehx2naf.onion:8333 -pvj4xk4zc4cr6jqq.onion:8333 -pwehxzv6422ezwky.onion:8333 -pxvkjp6sfb2f7foe.onion:8333 -pymhrtleulgjtjpv.onion:8333 -q5awmutvfbnpochk.onion:8333 -q6qcicmthsnhliyk.onion:8333 -qc62e5rl43jl7zeg.onion:8333 -qfrw5hbtebtylguz.onion:8333 -qfyca3b5c5n3z4cj.onion:8333 -qhurfs7xq5ttt3gw.onion:8333 -qjvuiw2wqh4hpzdf.onion:8333 -qkn35rb3x2gxbwq4.onion:8333 -qm6ojxpwmldyufwu.onion:8333 -qmqgsaua2z3325ra.onion:8333 -qnvoopilmeeuynvb.onion:8333 -qogcz4usci3gazle.onion:8333 -qqidfly4ogvkuw2c.onion:8333 -qs7mfrvh2lfgbicp.onion:8333 -qwr3zgp2a4rmqqgo.onion:8333 -qxjda3nslo2epmq7.onion:8333 -qyc5hagfewokqj4b.onion:8333 -qzprcdajdpwrhc5s.onion:8333 -r5uqjjv4tbhdchtk.onion:8333 -rbr65fyqbyacqlei.onion:8333 -rc2xxpwarefb3grb.onion:8333 -rcfefihpvdaamubz.onion:8333 -rcjaylqlw52gncpd.onion:8333 -recgbzfmpnq6ym27.onion:8333 -rgeepodser3wmqhx.onion:8333 -rhpb5rl5lbtdmxvu.onion:8333 -rijit2q4nhznnbiv.onion:8333 -rj4xermfmdtuezrw.onion:8333 -rjw6vpw5ffoncxuh.onion:8333 -rk4vbyca7xnn3top.onion:8333 -rkdvqcrtfv6yt4oy.onion:8333 -rpodz5ahuf72tjh7.onion:8333 -rqwty3g4ggf3yv5f.onion:8333 -rr4cj2exjcd7n4oa.onion:8333 -ruq62zoxgbsx4fvg.onion:8333 -ry5iycimq2r5ygfk.onion:8333 -rz2h45tnsads5zin.onion:8333 -rzo56opdnx7i6l3j.onion:8333 -s3fp6vs5mcjzf5ve.onion:8333 -s3ld37kwy3zgqgtq.onion:8333 -s3yelkvc5f5xeysw.onion:8333 -satofxsc3xjadxsm.onion:8333 -sbsqg4njc4fyxor7.onion:8333 -sexb46w2nehensmu.onion:8333 -sfrimrjhz5hsvuad.onion:8333 -shxelkjtfxafyof5.onion:8333 -sis2o73ppdrxphs3.onion:8333 -siu7ajff2avodiym.onion:8333 -sj2yaitqgbaaqgmg.onion:8333 -skoifp4oj7l4osu5.onion:8333 -sl2vfsvfghkc3g27.onion:8333 -slxg3yczhgliy3po.onion:8333 -smhdbzlekdihngfu.onion:8333 -spmhuxjb2cd7leun.onion:8333 -sqsdkbxbasyqgjkn.onion:8333 -srkgyv5edn2pa7il.onion:8333 -stiokrpjporb6dpb.onion:8333 -sxqjubmum4rmfgpu.onion:8333 -t245vi742ti3tnka.onion:8333 -t2pgkgoiwn736jcp.onion:8333 -t4cy5qirt6hhly52.onion:8333 -t4rqyf2dm25ewv2q.onion:8333 -t5fpovzc5ol6xgt5.onion:8333 -tfazohqfh6wszy3k.onion:8333 -tgjslnjoaaujhpjp.onion:8333 -th6fxymtwnfifqeu.onion:8333 -tha2oa3jj4ozk437.onion:8333 -thbjka62axzuwtnt.onion:8333 -tilhcdwqmwtbwie2.onion:8333 -tjxnkwdc46sq76gk.onion:8333 -tlnj6dhbutw4xozh.onion:8333 -toci5qgahb66kz33.onion:8333 -tuwj7ju4s25345pg.onion:8333 -tuxhgfve5b5kd27i.onion:8333 -tv6rvpgubnovlmmv.onion:8333 -txwqot2ksfhmp6mn.onion:8333 -tyiunn36lmfcq5lr.onion:8333 -u6bhivakdm6bzqr5.onion:8333 -ucdsqpykyog2j6h4.onion:8333 -uiccvhdj6ryfe523.onion:8333 -ukrjjhwodl44wmof.onion:8333 -ul5gm2ixy7kqdfwg.onion:8333 -unlo36oyamspg6hx.onion:8333 -unpto7zguim7usjx.onion:8333 -uoailgcebjuws47e.onion:8333 -upbjzazoaw3kt7xf.onion:8333 -upl6deoc6tfsbvrd.onion:8333 -uqlmbnkp3brmxl4t.onion:8333 -urtcrp7gpg7so6eb.onion:8335 -usazs7glm7geyxkl.onion:8333 -uwawjx7vp6hqrald.onion:8333 -uwxihtmgxtcltdjq.onion:8333 -v2lkjb7dgrwbk3nu.onion:8333 -v3zdzmhiguvyrs4b.onion:8333 -v5l4mksyb6d5zm4z.onion:8333 -v75sxxpysryq4tj2.onion:8333 -vaexpr25vjwa5333.onion:8333 -vhpg4gmdnz6d7qyv.onion:8333 -vp3ehqtaayl3pou6.onion:8333 -vpi5dsxsahu6n57y.onion:8333 -vrbl6tkn54c67qpz.onion:8333 -vs573vvh4brenqnq.onion:8333 -vtrhjstechqdkzt5.onion:8333 -vuf2vduepk4brfi4.onion:8333 -vunubqkfms7sifok.onion:8333 -vvqiioauoeuinuti.onion:8333 -vwpcfguewxhky4iy.onion:8333 -vwuxcj3smui4zqop.onion:8333 -vyxoizdzavp3obau.onion:8333 -vzfa474foeamygyu.onion:8333 -vzw3aimpwimuvo5r.onion:8333 -w5mdkvxl7kwmxvnv.onion:8333 -wahjf5o6pctm6mok.onion:8333 -wb6fyge5mkyneunc.onion:8333 -wba2xw6uerd66uzi.onion:8333 -wgzwu3vg3i54wayx.onion:8333 -wjpkhpyt2gd4jtid.onion:8333 -wjsbxxgg7yteubse.onion:8333 -wmrovbl4q2ocy6hb.onion:8333 -wo2bch52ht3yszmi.onion:8333 -worwipg6c7gzlope.onion:8333 -wpvksrcm6qghhjsf.onion:8333 -wqfdux3y5oyrwwol.onion:8333 -wqgfo3mnmv3kwtmt.onion:8333 -ws3dvlmkyvodjx6d.onion:8333 -ww3woycgzlbtvyt4.onion:8333 -wxheniw7sezhs5za.onion:8333 -wxxd2tbjhpkcga42.onion:8333 -wykoudmcnxbv3o2e.onion:8333 -wz5igvnbo2qre63v.onion:8333 -wzbll26zuafefpq2.onion:8333 -wzn23tspmap7h3aw.onion:8333 -x3ngb3va7dovuenw.onion:8333 -x6h4fiw5emtvr3hp.onion:8333 -xab45bpmmrl5g3by.onion:8333 -xakwj5e2fqjxwzeo.onion:8333 -xdbcejppfclheev2.onion:8333 -xfyobg6sskqtwitx.onion:8333 -xmneug3i56y42mpt.onion:8333 -xnlu3tvakngy7tkp.onion:8333 -xnuk3xxkc5vd4e2s.onion:8333 -xo3vmxwwuojy6lub.onion:8333 -xoxoxka3hgpokemn.onion:8333 -xp77eelk2nj4peqh.onion:9333 -xpi7nnlp4pu4ksk4.onion:8333 -xqwidygjt7qaitv2.onion:8333 -xtftq7tpqzhfavbt.onion:8333 -xudkoztdfrsuyyou.onion:8333 -xy4ks5vnwk7bams5.onion:8333 -xzm6lcvzm5b7auxr.onion:8333 -y27gfcs7mvdcpcf2.onion:8333 -y4arubp7qfgfargg.onion:8333 -y4jddmun5zum7e3b.onion:8333 -y4swmsaxdcos2bnu.onion:8333 -y4u7xlidil4ns7an.onion:8333 -y632nkryqa7wzpcd.onion:8333 -y7oz3ydnvib4xhbb.onion:8333 -ya44bby42j6jzhmi.onion:8333 -yaghstmdhek2mb6u.onion:8333 -yaib55ek3ue6gzyt.onion:8333 -ybk42io3dhsgrua2.onion:8333 -ycuezbmvaxd2c4hl.onion:8333 -ycvc5jxnqrjg5kbq.onion:8333 -ydonogjpjd3me45v.onion:8333 -ydscw35avlov5j7h.onion:8333 -ydt6cgr4oqvvchce.onion:8333 -yfospf6q6hmnn6it.onion:8333 -yfz55enlipwaxzkv.onion:8333 -ygeqkg4inplsace3.onion:8333 -ygwcypmb2qiotrp3.onion:8333 -ykaicnykw2eb3w7a.onion:8333 -ykeizcsub2pf5ko4.onion:8333 -ym7inmovbrna4gco.onion:8333 -yovvtzng5f7ry2ym.onion:8333 -yrls64atdq2rbxxj.onion:8333 -ytpus4vx5w7j6wp2.onion:8333 -yu3mtjrqf5ado6y6.onion:8333 -yvg6zvut7xjwaow5.onion:8333 -yw4fw2kay2zajnoc.onion:8333 -z3rrwmy3sxgru2bx.onion:8333 -z3ywbadw46ndnxgh.onion:8333 -zaijfotxe3y22mpt.onion:8333 -zco22k2jjuqdjna4.onion:8333 -zd6pgq3tjo2ndzwh.onion:8333 -zdjdb5nxiwbj3y4w.onion:8333 -zdqkjthclscwqq4h.onion:8333 -zh7hvalcgvjpoaqm.onion:8333 -zkw4h4emzxozfdlx.onion:8333 -zlaho3shztl5kqwi.onion:8333 -zlqwjfj7qku6ig4t.onion:8333 -zmu477wq3af5fszv.onion:8333 -zqjvtxskxonu4kzv.onion:8333 -zrrak247ba2ockl6.onion:8333 -zvinf525hps6yhd2.onion:8333 -zvuofxrnbotwq266.onion:8333 -zzgl4tf6v2p3bvjy.onion:8333 \ No newline at end of file diff --git a/WalletWasabi/OnionSeeds/TestNetOnionSeeds.txt b/WalletWasabi/OnionSeeds/TestNetOnionSeeds.txt deleted file mode 100644 index 16be9fc340c..00000000000 --- a/WalletWasabi/OnionSeeds/TestNetOnionSeeds.txt +++ /dev/null @@ -1,16 +0,0 @@ -thfsmmn2jbitcoin.onion:18333 -it2pj4f7657g3rhi.onion:18333 -nkf5e6b7pl4jfd4a.onion:18333 -4zhkir2ofl7orfom.onion:18333 -t6xj6wilh4ytvcs7.onion:18333 -i6y6ivorwakd7nw3.onion:18333 -ubqj4rsu3nqtxmtp.onion:18333 -ocasutxnvl4lwegq.onion:18333 -cu6octp6yo754wda.onion:18333 -lkiggf5esgs7d5z6.onion:18333 -rhcv7q2quqn74zlr.onion:18333 -nq7cak6pufzs2ou2.onion:18333 -nec4kn4ghql7p7an.onion:18333 -qlllezfaif5cscnx.onion:18333 -zmtr2di735ngxpcl.onion:18333 -4syvownyxejvqbzn.onion:18333 diff --git a/WalletWasabi/Stores/BitcoinStore.cs b/WalletWasabi/Stores/BitcoinStore.cs index 560e73263b6..9817009022d 100644 --- a/WalletWasabi/Stores/BitcoinStore.cs +++ b/WalletWasabi/Stores/BitcoinStore.cs @@ -1,47 +1,31 @@ using NBitcoin; -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; using System.Threading.Tasks; using WalletWasabi.Backend.Models; using WalletWasabi.Blockchain.Blocks; using WalletWasabi.Blockchain.Mempool; using WalletWasabi.Blockchain.P2p; using WalletWasabi.Blockchain.Transactions; -using WalletWasabi.Helpers; using WalletWasabi.Logging; +using WalletWasabi.Wallets; namespace WalletWasabi.Stores { /// - /// The purpose of this class is to safely and performantly manage all the Bitcoin related data - /// that's being serialized to disk, like transactions, wallet files, keys, blocks, index files, etc... + /// The purpose of this class is to safely and efficiently manage all the Bitcoin related data + /// that's being serialized to disk, like transactions, wallet files, keys, blocks, index files, etc. /// public class BitcoinStore { public BitcoinStore( - string workFolderPath, - Network network, IndexStore indexStore, AllTransactionStore transactionStore, - MempoolService mempoolService) + MempoolService mempoolService, + IRepository blockRepository) { - WorkFolderPath = Guard.NotNullOrEmptyOrWhitespace(nameof(workFolderPath), workFolderPath, trim: true); - IoHelpers.EnsureDirectoryExists(WorkFolderPath); - - Network = Guard.NotNull(nameof(network), network); IndexStore = indexStore; TransactionStore = transactionStore; MempoolService = mempoolService; - } - - /// - /// Special constructor used by the mock version. - /// - internal BitcoinStore() - { - TransactionStore = new AllTransactionStoreMock(); + BlockRepository = blockRepository; } public bool IsInitialized { get; protected set; } @@ -52,6 +36,7 @@ internal BitcoinStore() public AllTransactionStore TransactionStore { get; } public SmartHeaderChain SmartHeaderChain => IndexStore.SmartHeaderChain; public MempoolService MempoolService { get; } + public IRepository BlockRepository { get; } /// /// This should not be a property, but a creator function, because it'll be cloned left and right by NBitcoin later. @@ -63,13 +48,10 @@ public virtual async Task InitializeAsync() { using (BenchmarkLogger.Measure()) { - var networkWorkFolderPath = Path.Combine(WorkFolderPath, Network.ToString()); - var indexStoreFolderPath = Path.Combine(networkWorkFolderPath, "IndexStore"); - var initTasks = new[] { - IndexStore.InitializeAsync(indexStoreFolderPath), - TransactionStore.InitializeAsync(networkWorkFolderPath, Network) + IndexStore.InitializeAsync(), + TransactionStore.InitializeAsync() }; await Task.WhenAll(initTasks).ConfigureAwait(false); diff --git a/WalletWasabi/Stores/BitcoinStoreMock.cs b/WalletWasabi/Stores/BitcoinStoreMock.cs deleted file mode 100644 index 65e54277da9..00000000000 --- a/WalletWasabi/Stores/BitcoinStoreMock.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace WalletWasabi.Stores -{ - /// - /// This class provides a Mock version of for - /// Unit tests that only need a dummy version of the class. - /// - public class BitcoinStoreMock : BitcoinStore - { - public BitcoinStoreMock() : base() - { - } - } -} diff --git a/WalletWasabi/Stores/IndexStore.cs b/WalletWasabi/Stores/IndexStore.cs index fbff3680957..4deaaa68f3e 100644 --- a/WalletWasabi/Stores/IndexStore.cs +++ b/WalletWasabi/Stores/IndexStore.cs @@ -23,8 +23,11 @@ namespace WalletWasabi.Stores /// public class IndexStore { - public IndexStore(Network network, SmartHeaderChain hashChain) + public IndexStore(string workFolderPath, Network network, SmartHeaderChain hashChain) { + WorkFolderPath = Guard.NotNullOrEmptyOrWhitespace(nameof(workFolderPath), workFolderPath, trim: true); + IoHelpers.EnsureDirectoryExists(WorkFolderPath); + Network = Guard.NotNull(nameof(network), network); SmartHeaderChain = Guard.NotNull(nameof(hashChain), hashChain); } @@ -50,7 +53,6 @@ public async Task InitializeAsync(string workFolderPath, Task start { using (BenchmarkLogger.Measure()) { - WorkFolderPath = Guard.NotNullOrEmptyOrWhitespace(nameof(workFolderPath), workFolderPath, trim: true); var indexFilePath = Path.Combine(WorkFolderPath, "MatureIndex.dat"); MatureIndexFileManager = new DigestableSafeMutexIoManager(indexFilePath, digestRandomIndex: -1); var immatureIndexFilePath = Path.Combine(WorkFolderPath, "ImmatureIndex.dat"); diff --git a/WalletWasabi/TorDaemons/data-folder.zip b/WalletWasabi/TorDaemons/data-folder.zip index a6a2f22fce2..50fbfedd3c2 100644 Binary files a/WalletWasabi/TorDaemons/data-folder.zip and b/WalletWasabi/TorDaemons/data-folder.zip differ diff --git a/WalletWasabi/TorDaemons/digests.txt b/WalletWasabi/TorDaemons/digests.txt index fd028c55975..fd7a2cb512c 100644 --- a/WalletWasabi/TorDaemons/digests.txt +++ b/WalletWasabi/TorDaemons/digests.txt @@ -1,3 +1,3 @@ -af7302d62fc1e47f79af8860541365f77547233404302a1e601e1f367e6e2888 -fe6d719e18bf3a963f0274de259b7e029f40e4fe778f4d170bba343eb491af00 -d244a89f7ca9da2259925affa0bd008d3cdea1d9dfd24cf53efffb7c1eadc169 +5d5f175bf154f5f4cd2c460b9d4ea80c8219d07441f626e13a4d01c1408a0c28 +957768c0a005a9b6a1db1c52fc747ce39303415b412128731965ab3932c1fff4 +f6c8a3bd07b939e7c736a1e811df907611b763d0f8dbfafa721d8bf6ebfec691 \ No newline at end of file diff --git a/WalletWasabi/TorDaemons/tor-linux64.zip b/WalletWasabi/TorDaemons/tor-linux64.zip index a6c5ca219cc..5a58de9b437 100644 Binary files a/WalletWasabi/TorDaemons/tor-linux64.zip and b/WalletWasabi/TorDaemons/tor-linux64.zip differ diff --git a/WalletWasabi/TorDaemons/tor-osx64.zip b/WalletWasabi/TorDaemons/tor-osx64.zip index bb0107e7fbd..31d42eb85d9 100644 Binary files a/WalletWasabi/TorDaemons/tor-osx64.zip and b/WalletWasabi/TorDaemons/tor-osx64.zip differ diff --git a/WalletWasabi/TorDaemons/tor-win64.zip b/WalletWasabi/TorDaemons/tor-win64.zip index 4c459bb12b4..cb84063d327 100644 Binary files a/WalletWasabi/TorDaemons/tor-win64.zip and b/WalletWasabi/TorDaemons/tor-win64.zip differ diff --git a/WalletWasabi/TorSocks5/TorHttpClient.cs b/WalletWasabi/TorSocks5/TorHttpClient.cs index 9beac63e66e..b824a715f50 100644 --- a/WalletWasabi/TorSocks5/TorHttpClient.cs +++ b/WalletWasabi/TorSocks5/TorHttpClient.cs @@ -173,7 +173,7 @@ public async Task SendAsync(HttpRequestMessage request, Can // https://tools.ietf.org/html/rfc7230#section-2.7.1 // A sender MUST NOT generate an "http" URI with an empty host identifier. - var host = Guard.NotNullOrEmptyOrWhitespace($"{nameof(request)}.{nameof(request.RequestUri)}.{nameof(request.RequestUri.DnsSafeHost)}", request.RequestUri.DnsSafeHost, trim: true); + string host = Guard.NotNullOrEmptyOrWhitespace($"{nameof(request)}.{nameof(request.RequestUri)}.{nameof(request.RequestUri.DnsSafeHost)}", request.RequestUri.DnsSafeHost, trim: true); // https://tools.ietf.org/html/rfc7230#section-2.6 // Intermediaries that process HTTP messages (i.e., all intermediaries @@ -181,10 +181,21 @@ public async Task SendAsync(HttpRequestMessage request, Can // in forwarded messages. request.Version = HttpProtocol.HTTP11.Version; - if (TorSocks5Client != null && !TorSocks5Client.IsConnected) + string requestScheme = request.RequestUri.Scheme; + if (TorSocks5Client != null) { - TorSocks5Client?.Dispose(); - TorSocks5Client = null; + bool toDispose = + !TorSocks5Client.IsConnected + || + (requestScheme == "http" && TorSocks5Client.Stream is SslStream) + || + (requestScheme == "https" && !(TorSocks5Client.Stream is SslStream)); + + if (toDispose) + { + TorSocks5Client?.Dispose(); + TorSocks5Client = null; + } } if (TorSocks5Client is null || !TorSocks5Client.IsConnected) @@ -195,27 +206,9 @@ public async Task SendAsync(HttpRequestMessage request, Can await TorSocks5Client.ConnectToDestinationAsync(host, request.RequestUri.Port).ConfigureAwait(false); Stream stream = TorSocks5Client.TcpClient.GetStream(); - if (request.RequestUri.Scheme == "https") + if (requestScheme == "https") { - SslStream sslStream; - // On Linux and OSX ignore certificate, because of a .NET Core bug - // This is a security vulnerability, has to be fixed as soon as the bug get fixed - // Details: - // https://github.com/dotnet/corefx/issues/21761 - // https://github.com/nopara73/DotNetTor/issues/4 - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - sslStream = new SslStream( - stream, - leaveInnerStreamOpen: true); - } - else - { - sslStream = new SslStream( - stream, - leaveInnerStreamOpen: true, - userCertificateValidationCallback: (a, b, c, d) => true); - } + SslStream sslStream = new SslStream(stream, leaveInnerStreamOpen: true); await sslStream .AuthenticateAsClientAsync( diff --git a/WalletWasabi/TorSocks5/TorProcessManager.cs b/WalletWasabi/TorSocks5/TorProcessManager.cs index 8010318fb93..c38b2ca4101 100644 --- a/WalletWasabi/TorSocks5/TorProcessManager.cs +++ b/WalletWasabi/TorSocks5/TorProcessManager.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using System.IO; +using System.Linq; using System.Net; using System.Net.Http; using System.Runtime.InteropServices; @@ -39,6 +40,7 @@ public TorProcessManager(EndPoint torSocks5EndPoint, string logFile) public string LogFile { get; } public static bool RequestFallbackAddressUsage { get; private set; } = false; + private DateTimeOffset? RequestFallbackSince { get; set; } = null; public Process TorProcess { get; private set; } @@ -78,23 +80,20 @@ public void Start(bool ensureRunning, string dataDir) var torDir = Path.Combine(dataDir, "tor"); var torDataDir = Path.Combine(dataDir, "tordata"); var torPath = ""; - var hashSourcePath = ""; + byte[] hashSourceBytes = null; var geoIpPath = ""; var geoIp6Path = ""; var fullBaseDirectory = EnvironmentHelpers.GetFullBaseDirectory(); + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { torPath = $@"{torDir}/Tor/tor"; - hashSourcePath = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) - ? $@"{torDir}/Tor/tor.real" - : $@"{torDir}/Tor/tor"; geoIpPath = $@"{torDir}/Data/Tor/geoip"; geoIp6Path = $@"{torDir}/Data/Tor/geoip6"; } else // If Windows { torPath = $@"{torDir}\Tor\tor.exe"; - hashSourcePath = $@"{torDir}\Tor\tor.exe"; geoIpPath = $@"{torDir}\Data\Tor\geoip"; geoIp6Path = $@"{torDir}\Data\Tor\geoip6"; } @@ -104,7 +103,14 @@ public void Start(bool ensureRunning, string dataDir) Logger.LogInfo($"Tor instance NOT found at '{torPath}'. Attempting to acquire it ..."); InstallTor(torDir); } - else if (!IoHelpers.CheckExpectedHash(hashSourcePath, Path.Combine(fullBaseDirectory, "TorDaemons"))) + + hashSourceBytes = File.ReadAllBytes(torPath); + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + hashSourceBytes = hashSourceBytes.Concat(File.ReadAllBytes($@"{torDir}/Tor/tor.real")).ToArray(); + } + + if (!IoHelpers.CheckExpectedHash(hashSourceBytes, Path.Combine(fullBaseDirectory, "TorDaemons"))) { Logger.LogInfo($"Updating Tor..."); @@ -266,10 +272,14 @@ public void StartMonitor(TimeSpan torMisbehaviorCheckPeriod, TimeSpan checkIfRun } // Check if it changed in the meantime... - if (TorHttpClient.LatestTorException is TorSocks5FailureResponseException torEx2 && torEx2.RepField == RepField.HostUnreachable) + if (TorHttpClient.LatestTorException is TorSocks5FailureResponseException torEx2 + && torEx2.RepField == RepField.HostUnreachable + && !RequestFallbackAddressUsage) { // Fallback here... RequestFallbackAddressUsage = true; + RequestFallbackSince = DateTimeOffset.UtcNow; + Logger.LogInfo($"Backend onion unreachable - using fallback mechanism."); } } } @@ -281,6 +291,17 @@ public void StartMonitor(TimeSpan torMisbehaviorCheckPeriod, TimeSpan checkIfRun } } } + else + { + if (RequestFallbackAddressUsage + && !(RequestFallbackSince is null) + && DateTimeOffset.UtcNow - RequestFallbackSince > TimeSpan.FromHours(24)) + { + Logger.LogInfo($"Disabling fallback mechanism, using backend's onion address."); + RequestFallbackAddressUsage = false; + RequestFallbackSince = null; + } + } } catch (Exception ex) when (ex is OperationCanceledException || ex is TaskCanceledException || ex is TimeoutException) { diff --git a/WalletWasabi/WalletWasabi.csproj b/WalletWasabi/WalletWasabi.csproj index c99a2368c42..cd589c6809f 100644 --- a/WalletWasabi/WalletWasabi.csproj +++ b/WalletWasabi/WalletWasabi.csproj @@ -57,12 +57,6 @@ PreserveNewest - - Always - - - Always - Always diff --git a/WalletWasabi/Wallets/FileSystemBlockRepository.cs b/WalletWasabi/Wallets/FileSystemBlockRepository.cs index 64b6d376266..0e41a302fd6 100644 --- a/WalletWasabi/Wallets/FileSystemBlockRepository.cs +++ b/WalletWasabi/Wallets/FileSystemBlockRepository.cs @@ -2,10 +2,12 @@ using NBitcoin.DataEncoders; using Nito.AsyncEx; using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using WalletWasabi.Helpers; using WalletWasabi.Logging; namespace WalletWasabi.Wallets @@ -15,17 +17,189 @@ namespace WalletWasabi.Wallets /// public class FileSystemBlockRepository : IRepository { - public FileSystemBlockRepository(string blocksFolderPath, Network network) + private const double MegaByte = 1024 * 1024; + + public FileSystemBlockRepository(string blocksFolderPath, Network network, long targetBlocksFolderSizeMb = 300) { - BlocksFolderPath = blocksFolderPath; - Network = network; - CreateFolders(); + using (BenchmarkLogger.Measure()) + { + BlocksFolderPath = blocksFolderPath; + Network = network; + CreateFolders(); + EnsureBackwardsCompatibility(); + Prune(targetBlocksFolderSizeMb); + } } public string BlocksFolderPath { get; } - public Network Network { get; } + private Network Network { get; } private AsyncLock BlockFolderLock { get; } = new AsyncLock(); + /// + /// Copies files one by one from BlocksNETWORK_NAME folder to BitcoinStore/NETWORK_NAME/Blocks if not already migrated. + /// + private void EnsureBackwardsCompatibility() + { + Logger.LogTrace(">"); + + try + { + string dataDir = EnvironmentHelpers.GetDataDir(Path.Combine("WalletWasabi", "Client")); + string wrongGlobalBlockFolderPath = Path.Combine(dataDir, "Blocks"); + string[] wrongBlockFolderPaths = new[] + { + // Before Wasabi 1.1.13 + Path.Combine(dataDir, $"Blocks{Network}"), + Path.Combine(wrongGlobalBlockFolderPath, Network.Name) + }; + + foreach (string wrongBlockFolderPath in wrongBlockFolderPaths.Where(x => Directory.Exists(x))) + { + MigrateBlocks(wrongBlockFolderPath); + } + + if (Directory.Exists(wrongGlobalBlockFolderPath)) + { + // If all networks successfully migrated, too, then delete the transactions folder, too. + if (!Directory.EnumerateFileSystemEntries(wrongGlobalBlockFolderPath).Any()) + { + Directory.Delete(wrongGlobalBlockFolderPath, recursive: true); + Logger.LogInfo($"Deleted '{wrongGlobalBlockFolderPath}' folder."); + } + else + { + Logger.LogTrace($"Cannot delete '{wrongGlobalBlockFolderPath}' folder as it is not empty."); + } + } + } + catch (Exception ex) + { + Logger.LogWarning("Backwards compatibility could not be ensured."); + Logger.LogWarning(ex); + } + + Logger.LogTrace("<"); + } + + private void MigrateBlocks(string blockFolderPath) + { + Logger.LogTrace($"Initiate migration of '{blockFolderPath}'"); + + int cntSuccess = 0; + int cntRedundant = 0; + int cntFailure = 0; + + foreach (string oldBlockFilePath in Directory.EnumerateFiles(blockFolderPath)) + { + try + { + MigrateBlock(oldBlockFilePath, ref cntSuccess, ref cntRedundant); + } + catch (Exception ex) + { + Logger.LogDebug($"'{oldBlockFilePath}' failed to migrate."); + Logger.LogDebug(ex); + cntFailure++; + } + } + + Directory.Delete(blockFolderPath, recursive: true); + + if (cntSuccess > 0) + { + Logger.LogInfo($"Successfully migrated {cntSuccess} blocks to '{BlocksFolderPath}'."); + } + + if (cntRedundant > 0) + { + Logger.LogInfo($"{cntRedundant} blocks were already in '{BlocksFolderPath}'."); + } + + if (cntFailure > 0) + { + Logger.LogDebug($"Failed to migrate {cntFailure} blocks to '{BlocksFolderPath}'."); + } + + Logger.LogInfo($"Deleted '{blockFolderPath}' folder."); + } + + private void MigrateBlock(string blockFilePath, ref int cntSuccess, ref int cntRedundant) + { + string fileName = Path.GetFileName(blockFilePath); + string newFilePath = Path.Combine(BlocksFolderPath, fileName); + + if (!File.Exists(newFilePath)) + { + Logger.LogTrace($"Migrate '{blockFilePath}' -> '{newFilePath}'."); + + // Unintuitively File.Move overwrite: false throws an IOException if the file already exists. + // https://docs.microsoft.com/en-us/dotnet/api/system.io.file.move?view=netcore-3.1 + File.Move(sourceFileName: blockFilePath, destFileName: newFilePath, overwrite: false); + cntSuccess++; + } + else + { + Logger.LogTrace($"'{newFilePath}' already exists. Skip migrating."); + cntRedundant++; + } + } + + /// + /// Prunes so that its size is at most MB. + /// + /// Max size of folder in mega bytes. + private void Prune(long maxFolderSizeMb) + { + Logger.LogTrace($"> {nameof(maxFolderSizeMb)}={maxFolderSizeMb}"); + + try + { + List fileInfoList = Directory.EnumerateFiles(BlocksFolderPath).Select(x => new FileInfo(x)).ToList(); + + // Invalidate file info cache as per: + // https://docs.microsoft.com/en-us/dotnet/api/system.io.filesysteminfo.lastaccesstimeutc?view=netcore-3.1#remarks + fileInfoList.ForEach(x => x.Refresh()); + + double sizeSumMb = 0; + int cntPruned = 0; + + foreach (FileInfo blockFile in fileInfoList.OrderByDescending(x => x.LastAccessTimeUtc)) + { + try + { + double fileSizeMb = blockFile.Length / MegaByte; + + if (sizeSumMb + fileSizeMb <= maxFolderSizeMb) // The file can stay stored. + { + sizeSumMb += fileSizeMb; + } + else if (sizeSumMb + fileSizeMb > maxFolderSizeMb) // Keeping the file would exceed the limit. + { + string blockHash = Path.GetFileNameWithoutExtension(blockFile.Name); + blockFile.Delete(); + Logger.LogTrace($"Pruned {blockHash}. {nameof(sizeSumMb)}={sizeSumMb}."); + cntPruned++; + } + } + catch (Exception ex) + { + Logger.LogWarning(ex); + } + } + + if (cntPruned > 0) + { + Logger.LogInfo($"Blocks folder was over {maxFolderSizeMb} MB. Deleted {cntPruned} blocks."); + } + } + catch (Exception ex) + { + Logger.LogWarning(ex); + } + + Logger.LogTrace($"<"); + } + /// /// Gets a bitcoin block from the file system. /// @@ -34,8 +208,8 @@ public FileSystemBlockRepository(string blocksFolderPath, Network network) /// The requested bitcoin block. public async Task GetAsync(uint256 hash, CancellationToken cancellationToken) { - // Try get the block - Block block = null; + // Try get the block. + Block? block = null; using (await BlockFolderLock.LockAsync().ConfigureAwait(false)) { var encoder = new HexEncoder(); @@ -44,8 +218,13 @@ public async Task GetAsync(uint256 hash, CancellationToken cancellationTo { try { - var blockBytes = await File.ReadAllBytesAsync(filePath, cancellationToken).ConfigureAwait(false); + byte[] blockBytes = await File.ReadAllBytesAsync(filePath, cancellationToken).ConfigureAwait(false); block = Block.Load(blockBytes, Network); + + new FileInfo(filePath) + { + LastAccessTimeUtc = DateTime.UtcNow + }; } catch { @@ -122,18 +301,19 @@ public async Task CountAsync(CancellationToken cancellationToken) private void CreateFolders() { - if (Directory.Exists(BlocksFolderPath)) + try { - if (Network == Network.RegTest) + if (Directory.Exists(BlocksFolderPath) && Network == Network.RegTest) { Directory.Delete(BlocksFolderPath, true); - Directory.CreateDirectory(BlocksFolderPath); } } - else + catch (Exception ex) { - Directory.CreateDirectory(BlocksFolderPath); + Logger.LogDebug(ex); } + + IoHelpers.EnsureDirectoryExists(BlocksFolderPath); } } } diff --git a/WalletWasabi/Wallets/Wallet.cs b/WalletWasabi/Wallets/Wallet.cs index e889d5ab35e..5c3a9308d2c 100644 --- a/WalletWasabi/Wallets/Wallet.cs +++ b/WalletWasabi/Wallets/Wallet.cs @@ -221,7 +221,7 @@ public BuildTransactionResult BuildTransaction( IEnumerable allowedInputs = null, IPayjoinClient payjoinClient = null) { - var builder = new TransactionFactory(Network, KeyManager, Coins, BitcoinStore, password, allowUnconfirmed); + var builder = new TransactionFactory(Network, KeyManager, Coins, BitcoinStore.TransactionStore, password, allowUnconfirmed); return builder.BuildTransaction( payments, () => diff --git a/WalletWasabi/Wallets/WalletManager.cs b/WalletWasabi/Wallets/WalletManager.cs index 14839a2e82f..60b55195c33 100644 --- a/WalletWasabi/Wallets/WalletManager.cs +++ b/WalletWasabi/Wallets/WalletManager.cs @@ -398,8 +398,10 @@ public IEnumerable CoinsByOutPoint(OutPoint input) var res = new List(); foreach (var wallet in Wallets.Where(x => x.Key.State == WalletState.Started)) { - SmartCoin coin = wallet.Key.Coins.GetByOutPoint(input); - res.Add(coin); + if (wallet.Key.Coins.TryGetByOutPoint(input, out var coin)) + { + res.Add(coin); + } } return res; diff --git a/WalletWasabi/WebClients/PayJoin/PayjoinClient.cs b/WalletWasabi/WebClients/PayJoin/PayjoinClient.cs index b90439be4db..ec1f3595d9a 100644 --- a/WalletWasabi/WebClients/PayJoin/PayjoinClient.cs +++ b/WalletWasabi/WebClients/PayJoin/PayjoinClient.cs @@ -92,7 +92,7 @@ public async Task RequestPayjoin(PSBT originalTx, IHDKey accountKey, Roote var request = new HttpRequestMessage(HttpMethod.Post, endpoint) { - Content = new StringContent(cloned.ToHex(), Encoding.UTF8, "text/plain") + Content = new StringContent(cloned.ToBase64(), Encoding.UTF8, "text/plain") }; HttpResponseMessage bpuResponse = await TorHttpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); diff --git a/WalletWasabi/packages.lock.json b/WalletWasabi/packages.lock.json index 45ee1a58aff..5e6b4b6c459 100644 --- a/WalletWasabi/packages.lock.json +++ b/WalletWasabi/packages.lock.json @@ -51,19 +51,19 @@ }, "NBitcoin": { "type": "Direct", - "requested": "[5.0.47, )", - "resolved": "5.0.47", - "contentHash": "fjEOFg2syu1AC2Q6NCeZ8GmwpDGtUeRTvbgiRyVQflm8NVSR/6X2mrFRu+KG/Q+77eq9c5K5ip081cnpuK9d4w==", + "requested": "[5.0.81, )", + "resolved": "5.0.81", + "contentHash": "sBOvupELGlaw5mr4sNW0q8kwc6ALlYaG6telvebDLU7+9DYYYaPiHS1L8gPIa4BKpSO/9STQkMRbKmW7DcAUfQ==", "dependencies": { "Microsoft.Extensions.Logging.Abstractions": "1.0.0", - "Newtonsoft.Json": "11.0.1" + "Newtonsoft.Json": "11.0.2" } }, "NBitcoin.Secp256k1": { "type": "Direct", - "requested": "[1.0.3, )", - "resolved": "1.0.3", - "contentHash": "TCRUf7C44H/Hy42Ad1g0Dt83EfEH0l+4OuDhnWrzVsPBNiM6s6YRHnHYT+0dxGZKxD0CgdCTZ5f9Z2Ml1RRGbw==" + "requested": "[1.0.10, )", + "resolved": "1.0.10", + "contentHash": "+CbOOtba1tv4p0G8uKRmwH4he5LXNtqfxdIrDi0RcVViR7HRTbaoDE7tJ7cAkg7pxuiNHGkpvtn+rFgkPxYgYw==" }, "System.Collections.Immutable": { "type": "Direct", diff --git a/azure-pipelines-linux.yml b/azure-pipelines-linux.yml index f9b21ce9b9c..7dc04f01f4f 100644 --- a/azure-pipelines-linux.yml +++ b/azure-pipelines-linux.yml @@ -7,7 +7,7 @@ variables: jobs: - job: Linux pool: - vmImage: 'ubuntu-16.04' + vmImage: 'ubuntu-20.04' steps: - task: UseDotNet@2 displayName: 'Install .NET Core 3'