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?
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.
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) {
filter
is ISMINE_SPENDABLE
or ISMINE_WATCHONLY
, regardless of the value of fUseCache
.
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];
GetCredit
and GetAvailableCredit
had separate caches, whereas now they share one.
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.
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];
m_cached
could be an int
, or better std::bitset
.
@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.
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)
explicit
. I’d remove = CREDIT
.
36+struct CachableAccount
37+{
38+ enum Type {
39+ CREDIT,
40+ DEBIT,
41+ } m_type{CREDIT};
enum class
?
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..
... const m_type
, meaning it can’t change after initialization.
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)
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/:
0m_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();
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.
Reset
inline, which basically has the same effect as what you are suggesting without exposing too many innards.
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]) {
return GetCachable(m_credit_immature, ISMINE_SPENDABLE, !fUseCache);
? (same in GetImmatureWatchOnlyCredit
)
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:
0enum CacheType { DEBIT, CREDIT, IMMATURE_CREDIT, AVAILABLE_CREDIT };
1CAmount 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:
0enum CacheType { DEBIT, CREDIT, IMMATURE_CREDIT, AVAILABLE_CREDIT };
1CAmount 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.
@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
0- mutable CachableAccount m_debit{CachableAccount::Type::DEBIT};
1- mutable CachableAccount m_credit{CachableAccount::Type::CREDIT};
2- mutable CachableAccount m_credit_immature{CachableAccount::Type::CREDIT};
3- mutable CachableAccount m_credit_available{CachableAccount::Type::CREDIT};
4+ 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:
0auto& 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:
0// Avoid caching ismine or NONE or ALL cases (could remove this check and simplify in the future).
1const bool allow_cache = filter == ISMINE_SPENDABLE || filter == ISMINE_WATCH_ONLY;
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;
fUseCache && allow_cache
in the one place the argument is used below to avoid having its value change in the middle of the function.
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 GetAmount
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.
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.
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 };
AmountType/Kind/Category/...
?
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)
0//! Cached amount subdivided into watchonly and spendable parts.
/**...*/
)
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
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.
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.
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.
36+/**
37+ * Cachable amount subdivided into watchonly and spendable parts.
38+ */
39+struct CachableAmount
40+{
41+ std::bitset<ISMINE_ENUM_ELEMENTS> m_cached;
NO
and ALL
are never (supposed to be) cached?
The two comments contradict each other:
0// NO and ALL are never (supposed to be) cached
0// 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
0-----BEGIN PGP SIGNED MESSAGE-----
1Hash: SHA512
2
3utACK 46a7d7b1983ebf0a98b96a09d83d9f63ab81dbcc
4-----BEGIN PGP SIGNATURE-----
5
6iQGzBAEBCgAdFiEE+rVPoUahrI9sLGYTzit1aX5ppUgFAlwqrYAACgkQzit1aX5p
7pUjt+Qv/ZmeTg9qRNFbvQvlhEqwC8YTlz79XldXWXALt5wZpPFVL721OfLpB0Dq3
8aGVwaxUsIICmWY7McMpixs/Q563+GX5+mnwAnq7R+lLMfz3ESAqExnfsouxVVdII
9PZaNXh9jvXbrDGzz4MkyJKgMa7hx54vcg180wy5MQHCB1DTxT5iDrFeSxVKLlGk2
10cQOqqeob93Xa7pABisE7FnCZkPo2YsRJAY8D57XUb+9W3uomJFUlVeJn6UjveMNI
11CyHMmO2zmICeWTYdlGn7BcGcEpMUM2zZs3fNgp2AnhixgeSCcL19HPq5kL/ASH7l
12ln3uguLDEspSUD7u7vLEoqQcfYT0QO4YwJkhvbFwhKUA2qFvnqQg61bVee+C2fro
13RrN2uodY6nhGa55ZtV+JmJDu4FqNqK3sBydGnz7LSGhNcsvJcW7f+DN/YoSssyHK
14fpNkz6Wyilsm0i1xw6lZ+XJn8hfk87G6Jvo/D+mK3howtKebC0u7npQ0WGp4N+rF
15Amjiv43T
16=ONE8
17-----END PGP SIGNATURE-----
re-utACK c9e6e7ed79886b702614e627a966fc9a4850f8f0 (Only change is comments)
0-----BEGIN PGP SIGNED MESSAGE-----
1Hash: SHA512
2
3re-utACK c9e6e7ed79886b702614e627a966fc9a4850f8f0 (Only change is comments)
4-----BEGIN PGP SIGNATURE-----
5
6iQGzBAEBCgAdFiEE+rVPoUahrI9sLGYTzit1aX5ppUgFAlwqrYAACgkQzit1aX5p
7pUgtWwv+OiZ5fOCqUEVQRgsFHUXeXu+mFPLRT9TMP4HkO/fZbMgFg3gZUAMw9FCV
8R8973LZ2iSwK/IpWhrU/VA+jCvgQwIT0RP52qopIJKIb6JN+Bguf9Cw4Dx7nL+2u
9P393qaa6KIGcV/9REelCmWyIE3EgTnkTA/AJBAaDqvWqghMQAYrM3r19bDb9dHAe
105Xx+UF/FmmG/3xIuoh6EV75g2njVT/LbXRYYXOwjyF5nFbYN89CkZt1pENmbgF0l
11Fw7KRb/4LdhTGP72BIEz5lM645uXDXUZHDxKUWnE18hDP0AU2udGKaa+2vMvhxGh
12NmD1+J7/XEvvzpmLI/QhQH20HTQuNgxRVrxApxRWhrNaBcf6GBmF8MjhqX+1ekSL
13pSHbt35NfsnPGiwVVenqkBeP8DDt5S7tKioHTLyno7w1XuwOr750q2iCPo0ECVj+
143NXrmd/3kCM86CKn6l0p2EiuqpGs88Gb0asHL7P1nWN2BNnMGz1lss3U448uSHFa
15ealUr82R
16=9v90
17-----END PGP SIGNATURE-----