Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove brain farts #12234

Merged
merged 3 commits into from
Jan 17, 2024
Merged

Conversation

lontivero
Copy link
Collaborator

@lontivero lontivero commented Jan 12, 2024

This PR is for simplifying the HdPubKeyCache that contains unnecessary complexities that make it slow (the idea behind whatever is named blahblahCache is to make things faster.

The PR contains many things, changes and improvements all across the key management code because I think those are changes that we need to do anyway but by sure not all of them in the same PR.

One clear improvement in term of performance is in the HdPubKeyCache::GetView (a method that is called millions of times).

master (results in microseconds)

| Method  | WalletSize | Mean         | Error     | StdDev    |
|-------- |----------- |-------------:|----------:|----------:|
| GetView | 100        |     9.994 us | 0.0770 us | 0.0643 us |
| GetView | 1000       |    99.277 us | 1.2050 us | 1.0682 us |
| GetView | 10000      | 1,248.137 us | 5.1878 us | 4.5989 us |

PR (result in nanoseconds 1us = 1000 ns)

| Method  | WalletSize | Mean      | Error    | StdDev   | Median    |
|-------- |----------- |----------:|---------:|---------:|----------:|
| GetView | 100        |  92.55 ns | 1.907 ns | 4.495 ns |  91.07 ns |
| GetView | 1000       |  92.21 ns | 1.218 ns | 1.139 ns |  91.91 ns |
| GetView | 10000      | 108.51 ns | 1.284 ns | 1.201 ns | 107.79 ns |

About the new brain fart involving ArraySegment, that's there to avoid changing behavior. I mean, I think it is absolutely unnecessary but it is to avoid thinking and testing.

@kiminuo

This comment was marked as resolved.

@lontivero lontivero marked this pull request as ready for review January 12, 2024 17:24
@turbolay
Copy link
Collaborator

To help me reviewing, can you explain why a version using only a Dictionary in the HdPubKeyCache is not good, or can change behavior? turbolay@b75679d

@lontivero
Copy link
Collaborator Author

Using only a dictionary is possible but the assumption in the code is that the HdPubKeyCache always gives you an IEnumerable over a collection that never changes, in master this is achieved by returning a clone converted to an ImmutableList while the version in this PR achieves the same by using an ArraySegment over an array that only adds up so, the question is: how do you achieve the same using a dictionary?

@turbolay
Copy link
Collaborator

Thank you this is exactly the answer I needed. I will review soon

using System.Diagnostics.CodeAnalysis;
using System.Linq;
using NBitcoin;

namespace WalletWasabi.Blockchain.Keys;

public class HdPubKeyCache : IEnumerable<HdPubKey>
public class HdPubKeyCache : IEnumerable<HdPubKeyInfo>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great to have at least a few unit tests for this class. There is no HdPubKeyCacheTests file.

There we could verify the test scenario:

  1. Add 750 keys
  2. Add another 750 keys
  3. Verify that HdPubKeyCache.Snapshot for both previous states return the same first 750 keys (HdPubKeyInfo defines the == operator so it should be trivial)

This should work because ArraySegment holds reference to _hdPubKeyInfos.

Copy link
Collaborator Author

@lontivero lontivero Jan 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whatever we modify in the code has real consequences observable from the external world, otherwise why would someone modify code? This means that this should be observable through the KeyManager. You should be able to request the keys, generate a new key and verify that the enumeration that you received before adding the key doesn't contain it.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whatever we modify in the code has real consequences observable from the external world, otherwise why would someone modify code?

Lucas, you are good at arguing almost anything but we write tests1 for good reasons.

So would a test like this one https://github.com/kiminuo/WalletWasabi/tree/simplify-hdpkcache (kiminuo@6271bbf) hurt?

Personally, I would be also interested in

/// <summary>
/// Tests adding the same key twice. 
/// </summary>
[Fact]
public void SameKeyTwiceTest()
{
	KeyManager keyManager = ServiceFactory.CreateKeyManager(password: "password");
	HdPubKey NewKey(string label) => keyManager.GenerateNewKey(label, KeyState.Used, isInternal: true);
	HdPubKeyCache cache = new();

	HdPubKey key = NewKey($"Key#1");
	cache.AddKey(key, ScriptPubKeyType.Segwit);
	cache.AddKey(key, ScriptPubKeyType.Segwit);
}

if this should throw an exception or not. I understand that it should not ever happen but in your implementation there is

_hdPubKeyIndexedByScriptPubKey[info.ScriptPubKey] = info.HdPubKey;

which does not throw.

Footnotes

  1. Not as much as I would like lately but anyway.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. You are testing exactly as I suggested (testing the KeyManager). So, yes, except for the code added for testing only, which is not necessary.

The SameKeyTwiceTest is not good because it assumes the cache must fail in case of duplicated keys but that is not a responsibility of the cache. In case the cache receives a duplicated key that means that we have a bug in the KeyManager and we need to fix it.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SameKeyTwiceTest is not good because it assumes the cache must fail in case of duplicated keys but that is not a responsibility of the cache. In case the cache receives a duplicated key that means that we have a bug in the KeyManager and we need to fix it.

Well, and that's precisely what I'm after. The test would make it clear that we don't expect to get the same key twice. I'm not sure why it's not responsibility of cache to throw an exception in such case because it looks to me that in this scenario the internal state of cache would get broken.

However, I guess you don't watch such test. Anyway, my goal here is basically to document expectations. But we don't document code mostly so the alternative is to write a test but if even test is not possible then ...

@kiminuo
Copy link
Collaborator

kiminuo commented Jan 14, 2024

Looks like a great improvement to me.

Co-authored-by: Kimi <58662979+kiminuo@users.noreply.github.com>
Copy link
Collaborator

@turbolay turbolay left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, and I tested and didn't find problems.
My 2 suggestions are just for cf

WalletWasabi/Blockchain/Keys/HdPubKey.cs Outdated Show resolved Hide resolved
WalletWasabi/Blockchain/Keys/HdPubKey.cs Outdated Show resolved Hide resolved
Co-authored-by: Turbolay <turbolay@zksnacks.com>
private Dictionary<Script, HdPubKey> HdPubKeysByScript { get; } = new();
private HashSet<HdPubKey> HdPubKeys { get; } = new();
private Dictionary<KeyPath, ScriptBytesHdPubKeyPair> ScriptBytesHdPubKeyPairByKeyPath { get; } = new();
private Dictionary<Script, HdPubKey> _hdPubKeyIndexedByScriptPubKey = new(1_000);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a specific reason to break the code convention of "everything should be a property"?

{
ExtKey extKey = GetMasterExtKey(password);
var extKeysAndPubs = new List<(ExtKey secret, HdPubKey pubKey)>();
var extKeysAndPubs = new List<ExtKey>();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just extKeys?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delete the file.

@molnard
Copy link
Collaborator

molnard commented Jan 17, 2024

I loaded an old wallet and I got the following exc.

System.NullReferenceException
  HResult=0x80004003
  Message=Object reference not set to an instance of an object.
  Source=WalletWasabi
  StackTrace:
   at WalletWasabi.Blockchain.Keys.HdPubKeyCache.<>c.<get_HdPubKeys>b__6_0(HdPubKeyInfo x) in WalletWasabi\Blockchain\Keys\HdPubKeyCache.cs:line 19

image

image

@lontivero lontivero merged commit 56033a4 into WalletWasabi:master Jan 17, 2024
6 of 7 checks passed
This was referenced Jan 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants