This is a refactoring that will make #13756 a lot cleaner and straight-forward, since it adds another combination to the pile (watch-only * spendable * reused).
It's also a nice change in general.
35 | +{ 36 | + bool m_cached[4] = {0,0,0,0}; 37 | + CAmount m_value[4]; 38 | + void Reset() 39 | + { 40 | + memset(m_cached, 0, 4);
4 * sizeof(bool) -- sizeof(bool) is not required by the standard to be 1. (And even though that would be stupid, the standard is pretty explicit about it.)
Or perhaps instead, more typesafe / c++ style: std::fill(std::begin(m_cached), std::end(m_cached), false);, which I have not tested. This version would also fix the need for the constant "4" here.
1923 | @@ -1924,33 +1924,25 @@ std::set<uint256> CWalletTx::GetConflicts() const 1924 | return result; 1925 | } 1926 | 1927 | +CAmount CWalletTx::GetCachable(CachableAccount& acct, bool credit, const isminefilter& filter, bool recalculate) const
I know that various people have different feelings about this practice, but invocations would be more readable and less error-prone if bool credit were an enum class of CREDIT or DEBIT.
(I feel less strongly about recalculate because it's unambiguous which direction is "off" vs "on", and it defaults to "off".)
In fact, better still -- since any given CachableAccount is either credit or debit, why not have the account carry the flag, and remove the error-proneness of passing it by hand entirely?
Great idea, done.
31 | isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey); 32 | isminetype IsMine(const CKeyStore& keystore, const CTxDestination& dest); 33 | 34 | +struct CachableAccount 35 | +{ 36 | + bool m_cached[4] = {0,0,0,0};
I know it seems obvious now, but it would probably help future readers a bit to comment the fact that these correspond to the four values of isminetype. (Or you could add ISMINE_ENUM_ELEMENTS or the like and use that instead of the constant 4.)
In fact, actually, after studying the code -- we aren't really using a bitfield of IsMine types here. Only ISMINE_SPENDABLE and ISMINE_WATCHONLY are valid. Neither ISMINE_NO nor ISMINE_ALL are things we would ever want to cache here. Although I see that in #13756 you are maybe making more use of the fact that this is a bitfield, and I haven't read that one, so I assume that informs your choices here.
Yes, there is a tiny bit of waste due to ISMINE_NO and _ALL, and yep I am preparing to use this more extensively for the avoid_reuse feature in #13756. I could make a "bridge" that makes use of these more efficiently, but I kind of enjoy the simplicity of the direct mapping.
Should add that I adopted the ISMINE_ENUM_ELEMENTS idea.
2003 | @@ -2038,22 +2004,20 @@ CAmount CWalletTx::GetAvailableCredit(interfaces::Chain::Lock& locked_chain, boo 2004 | } 2005 | } 2006 | 2007 | - if (cache) { 2008 | - *cache = nCredit; 2009 | - assert(cache_used); 2010 | - *cache_used = true; 2011 | + if (fUseCache) {
The old code appears to always store into the cache, as long as filter is ISMINE_SPENDABLE or ISMINE_WATCHONLY, regardless of the value of fUseCache.
Oh, you're right. Good catch.
1999 | - } 2000 | - 2001 | - if (fUseCache && cache_used && *cache_used) { 2002 | - return *cache; 2003 | + if (fUseCache && m_credit.m_cached[filter]) { 2004 | + return m_credit.m_value[filter];
I don't know this code well enough to speak to the existing semantics of it. But it seems like, in the old code, GetCredit and GetAvailableCredit had separate caches, whereas now they share one.
Yep, you're correct! Fixing.
<!--e57a25ab6845829454e8d69fc972939a-->
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.
<!--174a7506f384e20aa4161008e828411d-->
Reviewers, this pull request conflicts with the following ones:
If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.
22 | - ISMINE_ALL = ISMINE_WATCH_ONLY | ISMINE_SPENDABLE 23 | + ISMINE_NO = 0, 24 | + ISMINE_WATCH_ONLY = 1 << 0, 25 | + ISMINE_SPENDABLE = 1 << 1, 26 | + ISMINE_ALL = ISMINE_WATCH_ONLY | ISMINE_SPENDABLE, 27 | + ISMINE_ENUM_ELEMENTS = ISMINE_ALL + 1,
I may be wrong but I think you can drop = ISMINE_ALL + 1.
nit, either align all = or none?
36 | +{ 37 | + enum Type { 38 | + CREDIT, 39 | + DEBIT, 40 | + } m_type{CREDIT}; 41 | + bool m_cached[ISMINE_ENUM_ELEMENTS];
nit, m_cached could be an int, or better std::bitset.
Concept ACK! this groups similar functionality together making it, overall, easier to understand. I was afraid for a bit with the word "accounts" though (having just finally deprecated that).
@laanwj Maybe I should have picked a different name! I think "account" works in this case, though... :)
Yes to be clear it was not a suggestion to change it :slightly_smiling_face: Would only have been a potential concern if it would end up in the RPC API.
utACK 3b9a9a4
39 | + CREDIT, 40 | + DEBIT, 41 | + } m_type{CREDIT}; 42 | + std::bitset<ISMINE_ENUM_ELEMENTS> m_cached; 43 | + CAmount m_value[ISMINE_ENUM_ELEMENTS]; 44 | + CachableAccount(Type type = CREDIT) : m_type(type)
Add explicit. I'd remove = CREDIT.
36 | +struct CachableAccount 37 | +{ 38 | + enum Type { 39 | + CREDIT, 40 | + DEBIT, 41 | + } m_type{CREDIT};
Remove, it is initialized in the constructor. Also, should be const.
Could be an enum class?
Made enum class but not const. It doesn't look like 'const enum class' ever occurs anywhere else, so assuming it's not a thing you do..
I mean ... const m_type, meaning it can't change after initialization.
Ah, I see.
41 | + } m_type{CREDIT}; 42 | + std::bitset<ISMINE_ENUM_ELEMENTS> m_cached; 43 | + CAmount m_value[ISMINE_ENUM_ELEMENTS]; 44 | + CachableAccount(Type type = CREDIT) : m_type(type) 45 | + { 46 | + Reset();
std::bitset is already initialized with zeros.
47 | + } 48 | + void Reset() 49 | + { 50 | + m_cached.reset(); 51 | + } 52 | + CachableAccount& Set(const isminefilter& filter, CAmount value)
I don't see a reason to pass enum values by const reference? (despite the fact there's some occurrences in the code).
I don't see the return value being used so I'd drop it.
@promag @MarcoFalke Thank you for the review! I believe I addressed all your feedback.
46 | + { 47 | + m_cached.reset(); 48 | + } 49 | + void Set(isminefilter filter, CAmount value) 50 | + { 51 | + m_cached[filter] = true;
nit, use http://www.cplusplus.com/reference/bitset/bitset/set/:
m_cached.set(filter)
42 | + std::bitset<ISMINE_ENUM_ELEMENTS> m_cached; 43 | + CAmount m_value[ISMINE_ENUM_ELEMENTS]; 44 | + explicit CachableAccount(Type type) : m_type(type) {} 45 | + void Reset() 46 | + { 47 | + m_cached.reset();
Since CachableAccount members are public you could ditch Reset and call m_cached.reset() directly. Otherwise you could make members private and add IsCached(), Get() and Type() accessors.
I think leaving it public is ok, but I can be convinced to make it private. For now, I made Reset inline, which basically has the same effect as what you are suggesting without exposing too many innards.
utACK fda0ade00739f30bdfa9d576371a350c1c4ade5f.
1990 | - if (fUseCache && fImmatureCreditCached) 1991 | - return nImmatureCreditCached; 1992 | - nImmatureCreditCached = pwallet->GetCredit(*tx, ISMINE_SPENDABLE); 1993 | - fImmatureCreditCached = true; 1994 | - return nImmatureCreditCached; 1995 | + if (fUseCache && m_credit_immature.m_cached[ISMINE_SPENDABLE]) {
Why not simplify to just return GetCachable(m_credit_immature, ISMINE_SPENDABLE, !fUseCache);? (same in GetImmatureWatchOnlyCredit)
Good point! Fixed.
utACK https://github.com/bitcoin/bitcoin/pull/15780/commits/1603501eaa6b292b3dc75e96836568ecc7cc73f6, I like this cleanup :)
utACK 18f23ae0c5599f2246e58e5293f7426471b73e9c, only change is to use GetCachable in GetImmatureCredit and
GetImmatureWatchOnlyCredit`.
33 | isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey); 34 | isminetype IsMine(const CKeyStore& keystore, const CTxDestination& dest); 35 | 36 | +struct CachableAccount 37 | +{ 38 | + enum class Type : uint8_t {
It doesn't make sense for CachableAccount to contain a Type enum, or an m_type runtime member. The type of cache variable isn't a runtime value that needs to be stored. This is kind of like declaring a fixed width variable to store an int, and then declaring another variable to store the fixed width.
The only place m_type is used is inside the GetCachable() function, so type could just be an argument to that function. This would make GetCachable() calls more straightforward to understand, because you wouldn't have to look in a separate part of the code for CachableAccount object construction to see the type of call. You could also simplify the GetCachable() by dropping the cached variable reference argument. So I guess I'm suggesting:
enum CacheType { DEBIT, CREDIT, IMMATURE_CREDIT, AVAILABLE_CREDIT };
CAmount GetCachable(CacheType type, const isminefilter& filter, bool recalculate) const;
The code is this way because I specifically objected to passing the CacheType to GetCacheable, which is how it was written before, because the CacheType is in fact a property of the CachableAccount object: any given such object is always either a credit account or a debit account, and it is always an error to call GetCachable with any CacheType other than the correct one for the particular CachableAccount object. Associating the type with the object prevents this error.
What I thought you were getting at in the first paragraph was that the type of a CachableAccount is a static property that is known at compile time. This I agree with, and you could certainly make it a type parameter, although I think that's unusually esoteric for C++ code, and making it an ordinary member is perfectly defensible. But I think not storing it at all, and leaving it implicit, is not defensible.
@gwillen it sounds like you are worried about an error that could have occurred in a previous or hypothetical version of the code where GetCacheable function took both CachableAmount and Type arguments. I agree that this would be more error prone than the current code, but it's not what I'm suggesting.
The current code avoids one problem (type being inconsistent with CachableAmount variable) but stores extra runtime data for no reason and adds a weird action-at-distance thing where you have to look 3 different places to see what GetCacheable will do (call site, function definition, cache variable construction). I don't there is anything wrong with actual interface I suggested:
enum CacheType { DEBIT, CREDIT, IMMATURE_CREDIT, AVAILABLE_CREDIT };
CAmount GetCachable(CacheType type, const isminefilter& filter, bool recalculate) const;
If you don't like it for some reason, that's fine. I already acked this PR in its current form. But if you want to call my suggestion "not defensible" you should be able to point out some actual error or mistake or problem that could it could cause.
It was not hypothetical, it was indeed the previous version of the code, which I think you can see in the history of the PR (but maybe a rebase wiped it out.) I responded too quickly because I was frustrated that you appeared to be suggesting to go back to a previous version, without addressing my comments discussing why I was opposed to the previous version. In fact I did misunderstand your suggestion, and the thing you actually suggested makes sense to me. Sorry for responding the way I did.
@ryanofsky That sounds reasonable, but how were you envisioning accessing the corresponding cache type from inside GetCachable? I started rewriting the code and ended up with
- mutable CachableAccount m_debit{CachableAccount::Type::DEBIT};
- mutable CachableAccount m_credit{CachableAccount::Type::CREDIT};
- mutable CachableAccount m_credit_immature{CachableAccount::Type::CREDIT};
- mutable CachableAccount m_credit_available{CachableAccount::Type::CREDIT};
+ mutable CachableAccount accounts[CACHETYPE_ENUM_ELEMENTS];
which seems like it will degrade readability.
re: https://github.com/bitcoin/bitcoin/pull/15780/files#r275588760 from kallewoof
I started rewriting the code and ended up with
Yes, I would probably implement this with an array. But a smaller change would be to use a conditional:
auto& cache = type == CREDIT ? m_credit : type == DEBIT ? m_debit : ...;
which seems like it will degrade readability.
Where specifically do you think this decreases readability?
1977 | @@ -2006,23 +1978,15 @@ CAmount CWalletTx::GetAvailableCredit(interfaces::Chain::Lock& locked_chain, boo 1978 | if (pwallet == nullptr) 1979 | return 0; 1980 | 1981 | + bool got_cache = filter == ISMINE_SPENDABLE || filter == ISMINE_WATCH_ONLY;
Is purpose of the got_cache variable just to avoid changing behavior in this PR? If so, would suggest renaming and commenting:
// Avoid caching ismine or NONE or ALL cases (could remove this check and simplify in the future).
const bool allow_cache = filter == ISMINE_SPENDABLE || filter == ISMINE_WATCH_ONLY;
Makes sense, changed.
1977 | @@ -2006,23 +1978,15 @@ CAmount CWalletTx::GetAvailableCredit(interfaces::Chain::Lock& locked_chain, boo 1978 | if (pwallet == nullptr) 1979 | return 0; 1980 | 1981 | + bool got_cache = filter == ISMINE_SPENDABLE || filter == ISMINE_WATCH_ONLY; 1982 | + fUseCache &= got_cache;
It seems pointless to overwrite the fUseCache argument. It would seem clearer just to write fUseCache && allow_cache in the one place the argument is used below to avoid having its value change in the middle of the function.
Good point, fixed.
utACK 18f23ae0c5599f2246e58e5293f7426471b73e9c. Overall this is an improvement and looks correct.
31 | typedef uint8_t isminefilter;
32 |
33 | isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey);
34 | isminetype IsMine(const CKeyStore& keystore, const CTxDestination& dest);
35 |
36 | +struct CachableAccount
I just noticed the struct is called CachableAccount and not CachableAmount. This seems like an unusual choice, because while it is clear this struct is a wrapper around an amount (subdivided into spendable and watchonly parts), it's at all not clear why we'd ever think of instances of these structs as "accounts." Not even to mention in the past history of accounts in the wallet...
I'd suggest at least renaming CachableAccount to CachableAmount, though I think more ideally this would:
CacheableAccount to CachedAmount and add doxygen comment: "Cached amount subdivided into watchonly and spendable parts."GetCachable to GetAmountIt is not always cached (hence the bitset), so I think "cachable" sounds better in this case. I agree on the amount vs account deal though. "An amount subdivided into spendable and watchonly parts" is exactly what this is.
re: #15780 (review) from kallewoof
It is not always cached (hence the bitset), so I think "cachable" sounds better in this case.
Ok, but this is kind of funny logic. I wonder if you also prefer to call pointers "pointables" because sometimes they don't point to valid objects.
If they would automatically deduce and/or instantiate the appropriate value/object when they didn't point to valid objects, they would definitely no longer just be "pointers".
utACK d7e11440052f44507a1b658350ee899986d1721d. Thanks for considering my suggestions. Would suggest squashing commits so the change is more readable when this is merged.
I left some new suggestions, but feel free to ignore them if not helpful.
@ryanofsky Thanks for the review! I tried using an array rather than 4 ivars, and it looks OK to me so sticking with that.
Finally someone did this. utACK f801212c7170fc56acdc0c61a0f33b5b103753a2 Looks like almost ready to merge. Would be great if @ryanofsky and @promag could accept/comment (on) the newest changes.
375 | - mutable bool fAvailableCreditCached; 376 | - mutable bool fWatchDebitCached; 377 | - mutable bool fWatchCreditCached; 378 | - mutable bool fImmatureWatchCreditCached; 379 | - mutable bool fAvailableWatchCreditCached; 380 | + enum CacheType { DEBIT, CREDIT, IMMATURE_CREDIT, AVAILABLE_CREDIT, CACHETYPE_ENUM_ELEMENTS };
This should be AmountType/Kind/Category/...?
Yeah, I agree. Changing to AmountType.
utACK f801212, nice simplifications over last review. Refactor looks correct, behavior is the same.
Would be nice if the benchmark could make it in first, so that we are not accidentally regressing on performance.
31 | typedef uint8_t isminefilter;
32 |
33 | isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey);
34 | isminetype IsMine(const CKeyStore& keystore, const CTxDestination& dest);
35 |
36 | +struct CachableAmount
Note: Could add doxygen comment suggested previously #15780 (review)
//! Cached amount subdivided into watchonly and spendable parts.
Oops, I missed that part. Thanks, done. (I used /**...*/)
1931 | @@ -1932,33 +1932,26 @@ std::set<uint256> CWalletTx::GetConflicts() const 1932 | return result; 1933 | } 1934 | 1935 | +CAmount CWalletTx::GetCachable(AmountType type, const isminefilter& filter, bool recalculate) const
Note: Could call this GetAmount instead of GetCachable as suggested previously #15780 (review) because this returns an amount and now handles caching internally instead of taking a cache reference.
The recalculate bool is for the cache, though. Maybe GetCachableAmount is better. Using that.
utACK d8fdf6b670e179282faff4ff57e37a6047efac9c. Thanks for the changes!
Would be nice if the benchmark could make it in first, so that we are not accidentally regressing on performance.
I'll review the benchmark, but I'd be pretty surprised if this affects performance significantly, since it is a basically a fancy variable rename, and doesn't really change existing logic.
utACK 46a7d7b1983ebf0a98b96a09d83d9f63ab81dbcc. Just method rename and new code comment since last review.
1999 | - if (fUseCache && fImmatureCreditCached) 2000 | - return nImmatureCreditCached; 2001 | - nImmatureCreditCached = pwallet->GetCredit(*tx, ISMINE_SPENDABLE); 2002 | - fImmatureCreditCached = true; 2003 | - return nImmatureCreditCached; 2004 | + return GetCachableAmount(IMMATURE_CREDIT, ISMINE_SPENDABLE, !fUseCache);
I don't want to suggest a change for this PR, since it's good that this just rearranges variables and doesn't change any computation.
But in the future, it seems like IMMATURE_CREDIT could be replaced with CREDIT here without affecting anything, and IMMATURE_CREDIT and AVAILABLE_CREDIT could go away. Unless I'm missing something, it seems like a lot of the previous variables like nImmatureCreditCached were just redundant.
Nice! That would definitely improve things. I agree that's beyond this PR but will gladly dig into it if nobody else does after merge.
utACK 46a7d7b.
36 | +/** 37 | + * Cachable amount subdivided into watchonly and spendable parts. 38 | + */ 39 | +struct CachableAmount 40 | +{ 41 | + std::bitset<ISMINE_ENUM_ELEMENTS> m_cached;
Could add a comment to say that NO and ALL are never (supposed to be) cached?
Thanks, done. I also fixed typo in wallet.cpp#1990 (said "Avoid caching ismine or NONE or all cases" before).
The two comments contradict each other:
// NO and ALL are never (supposed to be) cached
// Avoid caching ismine for NO or ALL cases (could remove this check and simplify in the future).
If it's harmful to cache these values, we should drop the second comment and explain the harm. If it's not harmful, we should drop the first comment because it's misleading and irrelevant.
In the future, it would be good to get rid of all unused variables and special cases by switching to a 2 part cache or just caching the 4 parts unconditionally.
utACK 46a7d7b1983ebf0a98b96a09d83d9f63ab81dbcc
<details><summary>Show signature</summary>
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512
utACK 46a7d7b1983ebf0a98b96a09d83d9f63ab81dbcc
-----BEGIN PGP SIGNATURE-----
iQGzBAEBCgAdFiEE+rVPoUahrI9sLGYTzit1aX5ppUgFAlwqrYAACgkQzit1aX5p
pUjt+Qv/ZmeTg9qRNFbvQvlhEqwC8YTlz79XldXWXALt5wZpPFVL721OfLpB0Dq3
aGVwaxUsIICmWY7McMpixs/Q563+GX5+mnwAnq7R+lLMfz3ESAqExnfsouxVVdII
PZaNXh9jvXbrDGzz4MkyJKgMa7hx54vcg180wy5MQHCB1DTxT5iDrFeSxVKLlGk2
cQOqqeob93Xa7pABisE7FnCZkPo2YsRJAY8D57XUb+9W3uomJFUlVeJn6UjveMNI
CyHMmO2zmICeWTYdlGn7BcGcEpMUM2zZs3fNgp2AnhixgeSCcL19HPq5kL/ASH7l
ln3uguLDEspSUD7u7vLEoqQcfYT0QO4YwJkhvbFwhKUA2qFvnqQg61bVee+C2fro
RrN2uodY6nhGa55ZtV+JmJDu4FqNqK3sBydGnz7LSGhNcsvJcW7f+DN/YoSssyHK
fpNkz6Wyilsm0i1xw6lZ+XJn8hfk87G6Jvo/D+mK3howtKebC0u7npQ0WGp4N+rF
Amjiv43T
=ONE8
-----END PGP SIGNATURE-----
</details>
utACK c9e6e7e.
re-utACK c9e6e7ed79886b702614e627a966fc9a4850f8f0 (Only change is comments)
<details><summary>Show signature</summary>
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512
re-utACK c9e6e7ed79886b702614e627a966fc9a4850f8f0 (Only change is comments)
-----BEGIN PGP SIGNATURE-----
iQGzBAEBCgAdFiEE+rVPoUahrI9sLGYTzit1aX5ppUgFAlwqrYAACgkQzit1aX5p
pUgtWwv+OiZ5fOCqUEVQRgsFHUXeXu+mFPLRT9TMP4HkO/fZbMgFg3gZUAMw9FCV
R8973LZ2iSwK/IpWhrU/VA+jCvgQwIT0RP52qopIJKIb6JN+Bguf9Cw4Dx7nL+2u
P393qaa6KIGcV/9REelCmWyIE3EgTnkTA/AJBAaDqvWqghMQAYrM3r19bDb9dHAe
5Xx+UF/FmmG/3xIuoh6EV75g2njVT/LbXRYYXOwjyF5nFbYN89CkZt1pENmbgF0l
Fw7KRb/4LdhTGP72BIEz5lM645uXDXUZHDxKUWnE18hDP0AU2udGKaa+2vMvhxGh
NmD1+J7/XEvvzpmLI/QhQH20HTQuNgxRVrxApxRWhrNaBcf6GBmF8MjhqX+1ekSL
pSHbt35NfsnPGiwVVenqkBeP8DDt5S7tKioHTLyno7w1XuwOr750q2iCPo0ECVj+
3NXrmd/3kCM86CKn6l0p2EiuqpGs88Gb0asHL7P1nWN2BNnMGz1lss3U448uSHFa
ealUr82R
=9v90
-----END PGP SIGNATURE-----
</details>
utACK c9e6e7ed79886b702614e627a966fc9a4850f8f0. Just comment changes since last review.