Skip to content

Commit

Permalink
Merge pull request WalletWasabi#12277 from SuperJMN/improvements/opti…
Browse files Browse the repository at this point in the history
…mize-startup-pull-based

Optimize application startup - alternate method
  • Loading branch information
RolandUI authored Jan 30, 2024
2 parents c5771c9 + 4f6435a commit 6a2de0a
Show file tree
Hide file tree
Showing 14 changed files with 98 additions and 272 deletions.
23 changes: 14 additions & 9 deletions WalletWasabi.Fluent/Models/Wallets/Address.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using NBitcoin;
Expand All @@ -12,13 +11,16 @@ namespace WalletWasabi.Fluent.Models.Wallets;

public class Address : ReactiveObject, IAddress
{
public Address(KeyManager keyManager, HdPubKey hdPubKey)
private readonly Action<Address> _onHide;

public Address(KeyManager keyManager, HdPubKey hdPubKey, Action<Address> onHide)
{
KeyManager = keyManager;
HdPubKey = hdPubKey;
Network = keyManager.GetNetwork();
HdFingerprint = KeyManager.MasterFingerprint;
BitcoinAddress = HdPubKey.GetAddress(Network);
_onHide = onHide;
}

public KeyManager KeyManager { get; }
Expand All @@ -31,15 +33,9 @@ public Address(KeyManager keyManager, HdPubKey hdPubKey)
public KeyPath FullKeyPath => HdPubKey.FullKeyPath;
public string Text => BitcoinAddress.ToString();

private bool IsUnused => Labels.Any() && !HdPubKey.IsInternal && HdPubKey.KeyState == KeyState.Clean;

public bool IsUsed => !IsUnused;

public void Hide()
{
KeyManager.SetKeyState(KeyState.Locked, HdPubKey);
KeyManager.ToFile();
this.RaisePropertyChanged(nameof(IsUsed));
_onHide(this);
}

public void SetLabels(LabelsArray labels)
Expand Down Expand Up @@ -77,4 +73,13 @@ public async Task ShowOnHwWalletAsync()
throw;
}
}

public override int GetHashCode() => Text.GetHashCode();

public override bool Equals(object? obj)
{
return obj is IAddress address && Equals(address);
}

protected bool Equals(IAddress other) => Text.Equals(other.Text);
}
80 changes: 54 additions & 26 deletions WalletWasabi.Fluent/Models/Wallets/AddressesModel.cs
Original file line number Diff line number Diff line change
@@ -1,47 +1,75 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using DynamicData;
using WalletWasabi.Blockchain.Keys;
using WalletWasabi.Fluent.Extensions;
using WalletWasabi.Blockchain.TransactionProcessing;
using WalletWasabi.Wallets;

namespace WalletWasabi.Fluent.Models.Wallets;

[AutoInterface]
public partial class AddressesModel : IDisposable
public partial class AddressesModel
{
private readonly CompositeDisposable _disposable = new();
private readonly KeyManager _keyManager;
private readonly ISubject<HdPubKey> _newAddressGenerated = new Subject<HdPubKey>();
private readonly Wallet _wallet;
private readonly SourceList<HdPubKey> _source;

public AddressesModel(IObservable<Unit> addressesUpdated, KeyManager keyManager)
public AddressesModel(Wallet wallet)
{
_keyManager = keyManager;
_wallet = wallet;
_source = new SourceList<HdPubKey>();
_source.AddRange(GetUnusedKeys());

Cache =
addressesUpdated.Fetch(GetAddresses, address => address.Text)
.DisposeWith(_disposable);
Observable.FromEventPattern<ProcessedResult>(
h => wallet.WalletRelevantTransactionProcessed += h,
h => wallet.WalletRelevantTransactionProcessed -= h)
.Do(_ => UpdateUnusedKeys())
.Subscribe();

UnusedAddressesCache =
Cache.Connect()
.AutoRefresh(x => x.IsUsed)
.Filter(x => !x.IsUsed)
.AsObservableCache()
.DisposeWith(_disposable);
_newAddressGenerated
.Do(address => _source.Add(address))
.Subscribe();

HasUnusedAddresses = UnusedAddressesCache.NotEmpty();
_source.Connect()
.Transform(key => (IAddress) new Address(_wallet.KeyManager, key, Hide))
.Bind(out var unusedAddresses)
.Subscribe();

Unused = unusedAddresses;
}

public IObservableCache<IAddress, string> Cache { get; }
private IEnumerable<HdPubKey> GetUnusedKeys() => _wallet.KeyManager.GetKeys(x => x is { IsInternal: false, KeyState: KeyState.Clean, Labels.Count: > 0 });

public IObservableCache<IAddress, string> UnusedAddressesCache { get; }
public IAddress NextReceiveAddress(IEnumerable<string> destinationLabels)
{
var pubKey = _wallet.GetNextReceiveAddress(destinationLabels);
var nextReceiveAddress = new Address(_wallet.KeyManager, pubKey, Hide);
_newAddressGenerated.OnNext(pubKey);

public IObservable<bool> HasUnusedAddresses { get; }
return nextReceiveAddress;
}

public void Dispose() => _disposable.Dispose();
public ReadOnlyObservableCollection<IAddress> Unused { get; }

private IEnumerable<IAddress> GetAddresses() => _keyManager
.GetKeys()
.Reverse()
.Select(x => new Address(_keyManager, x));
public void Hide(Address address)
{
_wallet.KeyManager.SetKeyState(KeyState.Locked, address.HdPubKey);
_wallet.KeyManager.ToFile();
_source.Remove(address.HdPubKey);
}

private void UpdateUnusedKeys()
{
var itemsToRemove = _source.Items
.Where(item => item.KeyState != KeyState.Clean)
.ToList();

foreach (var item in itemsToRemove)
{
_source.Remove(item);
}
}
}
2 changes: 0 additions & 2 deletions WalletWasabi.Fluent/Models/Wallets/IAddress.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ public interface IAddress : IReactiveObject

LabelsArray Labels { get; }

bool IsUsed { get; }

void Hide();

void SetLabels(LabelsArray labels);
Expand Down
19 changes: 3 additions & 16 deletions WalletWasabi.Fluent/Models/Wallets/WalletModel.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using NBitcoin;
using ReactiveUI;
using WalletWasabi.Fluent.Extensions;
using WalletWasabi.Fluent.Helpers;
using WalletWasabi.Fluent.ViewModels.Wallets.Labels;
using WalletWasabi.Wallets;

namespace WalletWasabi.Fluent.Models.Wallets;

public partial interface IWalletModel : INotifyPropertyChanged
{
}
public partial interface IWalletModel : INotifyPropertyChanged;

[AutoInterface]
public partial class WalletModel : ReactiveObject
Expand All @@ -39,7 +35,7 @@ public WalletModel(Wallet wallet, IAmountProvider amountProvider)

Transactions = new WalletTransactionsModel(this, wallet);

AddressesModel = new AddressesModel(Transactions.TransactionProcessed.ToSignal().Merge(_newAddressGenerated.ToSignal()), Wallet.KeyManager);
Addresses = new AddressesModel(Wallet);

State =
Observable.FromEventPattern<WalletState>(Wallet, nameof(Wallet.StateChanged))
Expand Down Expand Up @@ -69,7 +65,7 @@ public WalletModel(Wallet wallet, IAmountProvider amountProvider)
this.WhenAnyValue(x => x.Auth.IsLoggedIn).BindTo(this, x => x.IsLoggedIn);
}

public IAddressesModel AddressesModel { get; }
public IAddressesModel Addresses { get; }

internal Wallet Wallet { get; }

Expand Down Expand Up @@ -120,15 +116,6 @@ public IWalletInfoModel GetWalletInfo()
return new WalletInfoModel(Wallet);
}

public IAddress GetNextReceiveAddress(IEnumerable<string> destinationLabels)
{
var pubKey = Wallet.GetNextReceiveAddress(destinationLabels);
var nextReceiveAddress = new Address(Wallet.KeyManager, pubKey);
_newAddressGenerated.OnNext(nextReceiveAddress);

return nextReceiveAddress;
}

public void Rename(string newWalletName)
{
Services.WalletManager.RenameWallet(Wallet, newWalletName);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System.Collections.Generic;
using System.Reactive;
using System.Threading.Tasks;
using System.Windows.Input;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Threading.Tasks;
using System.Windows.Input;
using DynamicData;
using DynamicData.Binding;
using ReactiveUI;
using WalletWasabi.Blockchain.Analysis.Clustering;
using WalletWasabi.Fluent.Extensions;
Expand Down Expand Up @@ -63,12 +64,17 @@ public ReceiveAddressViewModel(UiContext uiContext, IWalletModel wallet, IAddres

protected override void OnNavigatedTo(bool isInHistory, CompositeDisposable disposables)
{
_wallet.AddressesModel.Cache
.Connect()
.AutoRefresh(x => x.IsUsed)
.Watch(Model.Text)
.Where(change => change.Current.IsUsed)
.Do(_ => Navigate().Back())
_wallet.Addresses.Unused
.ToObservableChangeSet()
.ObserveOn(RxApp.MainThreadScheduler)
.OnItemRemoved(
address =>
{
if (Equals(address, Model))
{
Navigate().BackTo<ReceiveViewModel>();
}
})
.Subscribe()
.DisposeWith(disposables);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
using System.Threading.Tasks;
using Avalonia.Controls;
using DynamicData;
using DynamicData.Binding;
using WalletWasabi.Fluent.Models.Wallets;
using WalletWasabi.Fluent.ViewModels.Dialogs.Base;
using WalletWasabi.Fluent.ViewModels.Navigation;

namespace WalletWasabi.Fluent.ViewModels.Wallets.Receive;
Expand All @@ -26,15 +26,14 @@ private ReceiveAddressesViewModel(IWalletModel wallet)

protected override void OnNavigatedTo(bool isInHistory, CompositeDisposable disposables)
{
_wallet
.AddressesModel.UnusedAddressesCache
.Connect()
_wallet.Addresses.Unused
.ToObservableChangeSet()
.Transform(CreateAddressViewModel)
.Bind(out var addresses)
.Bind(out var unusedAddresses)
.Subscribe()
.DisposeWith(disposables);

var source = ReceiveAddressesDataGridSource.Create(addresses);
var source = ReceiveAddressesDataGridSource.Create(unusedAddresses);

Source = source;
Source.RowSelection!.SingleSelect = true;
Expand All @@ -45,7 +44,7 @@ protected override void OnNavigatedTo(bool isInHistory, CompositeDisposable disp

private AddressViewModel CreateAddressViewModel(IAddress address)
{
return new AddressViewModel(UiContext, OnEditAddressAsync, address1 => OnShowAddressAsync(address1), address);
return new AddressViewModel(UiContext, OnEditAddressAsync, OnShowAddressAsync, address);
}

private void OnShowAddressAsync(IAddress a)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System.Reactive.Linq;
using System.Windows.Input;
using DynamicData.Binding;
using DynamicData.Aggregation;
using ReactiveUI;
using WalletWasabi.Fluent.Extensions;
using WalletWasabi.Fluent.Models.Wallets;
Expand Down Expand Up @@ -41,9 +43,7 @@ private ReceiveViewModel(IWalletModel wallet)

ShowExistingAddressesCommand = ReactiveCommand.Create(OnShowExistingAddresses);

AddressesModel = wallet.AddressesModel;

HasUnusedAddresses = _wallet.AddressesModel.HasUnusedAddresses.StartWith(false);
AddressesModel = wallet.Addresses;
}

public IAddressesModel AddressesModel { get; }
Expand All @@ -52,12 +52,12 @@ private ReceiveViewModel(IWalletModel wallet)

public ICommand ShowExistingAddressesCommand { get; }

public IObservable<bool> HasUnusedAddresses { get; }
public IObservable<bool> HasUnusedAddresses => _wallet.Addresses.Unused.ToObservableChangeSet().Count().Select(i => i > 0);

private void OnNext()
{
SuggestionLabels.ForceAdd = true;
var address = _wallet.GetNextReceiveAddress(SuggestionLabels.Labels);
var address = _wallet.Addresses.NextReceiveAddress(SuggestionLabels.Labels);
SuggestionLabels.Labels.Clear();

Navigate().To().ReceiveAddress(_wallet, address, Services.UiConfig.Autocopy);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
EnableBack="{Binding EnableBack}">
<DockPanel>
<Button DockPanel.Dock="Bottom" Focusable="False" Content="Addresses Awaiting Payment"
IsVisible="{Binding AddressesModel.HasUnusedAddresses^, FallbackValue=True}"
IsVisible="{Binding HasUnusedAddresses^, FallbackValue=False}"
Classes="h8 plain activeHyperLink" Command="{Binding ShowExistingAddressesCommand}" Cursor="Hand"
HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="0 0 0 0" />
<StackPanel HorizontalAlignment="Stretch" Spacing="10">
Expand Down
21 changes: 1 addition & 20 deletions WalletWasabi.Tests/UnitTests/ViewModels/AddressViewModelTests.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
using System.Threading.Tasks;
using Moq;
using WalletWasabi.Blockchain.Analysis.Clustering;
using WalletWasabi.Fluent.Models.Wallets;
using WalletWasabi.Fluent.ViewModels.Wallets.Receive;
using WalletWasabi.Tests.UnitTests.ViewModels.TestDoubles;
using Xunit;
Expand All @@ -10,29 +7,13 @@ namespace WalletWasabi.Tests.UnitTests.ViewModels;

public class AddressViewModelTests
{
[Fact]
public void HideCommandShouldInvokeCorrectMethod()
{
var address = Mock.Of<IAddress>(MockBehavior.Loose);
var context = new UiContextBuilder().WithDialogThatReturns(true).Build();
var sut = new AddressViewModel(
context,
_ => Task.CompletedTask,
_ => { },
address);

sut.HideAddressCommand.Execute(null);

Mock.Get(address).Verify(x => x.Hide(), Times.Once);
}

[Fact]
public void AddressPropertiesAreExposedCorrectly()
{
var testAddress = new TestAddress("ad");
var labels = new LabelsArray("Label 1", "Label 2");
testAddress.SetLabels(labels);
var sut = new AddressViewModel(MockUtils.ContextStub(), _ => Task.CompletedTask, _ => { }, testAddress);
var sut = new AddressViewModel(MockUtils.ContextStub(), async _ => { }, address => { }, testAddress);

Assert.Equal(testAddress.Text, sut.AddressText);
Assert.Equal(labels, sut.Labels);
Expand Down
Loading

0 comments on commit 6a2de0a

Please sign in to comment.