Skip to content

Conversation

@furszy
Copy link

@furszy furszy commented Jul 21, 2020

Built on top of #1755 and #1666 .

The goal of this PR has been to improve the CWalletTx to be able to add shielded cached balances in a much cleaner way.

Including the following changes:

@furszy furszy self-assigned this Jul 21, 2020
@Fuzzbawls Fuzzbawls changed the title [WIP] wallet: add cachable amounts for caching credit/debit values [WIP][Wallet] add cacheable amounts for caching credit/debit values Jul 26, 2020
@Fuzzbawls Fuzzbawls added this to the 5.0.0 milestone Jul 26, 2020
@furszy furszy force-pushed the 2020_abstract_out_isSolvable branch from 27cd183 to 506e310 Compare August 5, 2020 19:21
@furszy furszy changed the title [WIP][Wallet] add cacheable amounts for caching credit/debit values [Wallet] add cacheable amounts for caching credit/debit values Aug 5, 2020
furszy added 3 commits August 8, 2020 18:40
This is a modified version of btc@0c8ea6380c9f402ed9777fd015b117ba13125a35
Modified version of btc@6d714c3419b368671bd071a8992950c3dc00e613
Modified version of btc@4e91820531889e309dc4335fe0de8229c6426040
@furszy furszy force-pushed the 2020_abstract_out_isSolvable branch from 506e310 to 35de58c Compare August 8, 2020 22:08
@furszy
Copy link
Author

furszy commented Aug 8, 2020

rebased after get 1755 and #1666 merged. Ready for review.

Copy link

@random-zebra random-zebra left a comment

Choose a reason for hiding this comment

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

Very nice refactoring. Love the concept.
Few things (some of which can be tackled in a followup PR):

  • !fUseCache = recalculate (see comment inlined for GetColdStakingDebit/GetStakeDelegationDebit).
  • The change to GetUnlockedCredit/GetLockedCredit is wrong. But we should probably just remove those methods.
  • GetAvailableWatchOnlyCredit can be refactored in terms of GetCachableAmount or, this too, simply removed.
  • GetUnspentCredit should also be removed (as the maturity should not be checked here) and just replaced with GetCredit.

@furszy
Copy link
Author

furszy commented Aug 14, 2020

Great catches :) .
Agree on removing the methods that are not used (plus, not tested anywhere).

Some thoughts:
GetUnlockedCredit --> yes, this one can be removed.
GetLockedCredit --> not sure, will need to think on a replacement for this. Maybe a cached locked amount like what we have with the change amount and mark the tx dirty if it's unlocked/locked.
GetUnspentCredit --> not sure on removing this one. It's a simple wrapper that checks for maturity before get the cached amount. Side from this, totally agree on unifying it with GetCredit for sure.

@furszy furszy force-pushed the 2020_abstract_out_isSolvable branch from 35de58c to 1bf9dc4 Compare August 14, 2020 19:04
@random-zebra
Copy link

Yeah I don't think we should check for IsSpent in GetCredit. That should only be done in GetAvailableCredit / GetAvailableWatchOnlyCredit, and maturity should be checked there as well (no need for a separate GetUnspentCredit wrapper imo). But well... this can be tackled/discussed in a separate PR.

Fuzzbawls
Fuzzbawls previously approved these changes Aug 18, 2020
Copy link
Collaborator

@Fuzzbawls Fuzzbawls left a comment

Choose a reason for hiding this comment

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

ACK 1bf9dc4

furszy added a commit that referenced this pull request Aug 18, 2020
a13c245 AvailableCoins: remove unused "includeZeroValue" flag. (furszy)
e74dcd8 AvailableCoins: improving core readability. (furszy)

Pull request description:

  Built on top of #1759 .

  Improved code readability of the `CWallet::AvailableCoins` method decoupling nested statements and adding comments in between the checks.
  Plus, removed an unused "add zero value utxo" flag.

  An ugly area of the code could get pretty readable with some love.
  #1757 is going further on this area with some more improvements.

ACKs for top commit:
  random-zebra:
    utACK a13c245
  Fuzzbawls:
    utACK a13c245

Tree-SHA512: 1a5cb1eb058505d66b9a18de60ded211b248568150506e3b7ef32a5b3a8a818055ea85d321caa0c709ee7951dab12bf3450f057edadbf530e9514342194e55f8
@furszy furszy requested a review from random-zebra August 18, 2020 19:24
@random-zebra
Copy link

random-zebra commented Aug 19, 2020

I think that this PR highlights a problem with CWalletTx::GetCredit / CWalletTx::GetDebit and cold staking scripts.
These functions don't take into account that isminefilter is actually a bitflag and the conditions could overlap.

For example, calling tx.GetCredit(ISMINE_ALL) (which we do, e.g., from the GUI, for the tx description) would result in a bug with a P2CS output, if the wallet has both cold staking keys, where credit is added twice (third and fourth if block):

    CAmount credit = 0;
    if (filter & ISMINE_SPENDABLE) {
        // GetBalance can assume transactions in mapWallet won't change
        credit += GetCachableAmount(CREDIT, ISMINE_SPENDABLE, recalculate, fUnspent);
    }
    if (filter & ISMINE_WATCH_ONLY) {
        credit += GetCachableAmount(CREDIT, ISMINE_WATCH_ONLY, recalculate, fUnspent);
    }
    if (filter & ISMINE_COLD) {
        credit += GetCachableAmount(CREDIT, ISMINE_COLD, recalculate, fUnspent);
    }
    if (filter & ISMINE_SPENDABLE_DELEGATED) {
        credit += GetCachableAmount(CREDIT, ISMINE_SPENDABLE_DELEGATED, recalculate, fUnspent);
    }
    return credit;

Also, this way, ISMINE_SPENDABLE_STAKEABLE is never cached (same as with ISMINE_ALL).

Fixing this would mean rewriting them as just:

CAmount CWalletTx::GetCredit(const isminefilter& filter, bool recalculate, bool fUnspent) const
{
    return GetCachableAmount(CREDIT, filter, recalculate, fUnspent);
}

CAmount CWalletTx::GetDebit(const isminefilter& filter) const
{
    if (vin.empty())
        return 0;

    return GetCachableAmount(DEBIT, filter);
}

(note that to make the the wallet_tests pass and fake out IsFromMe(), with this change, you would need to .Set(ISMINE_ALL, 1) inside add_coin).

I'm not sure why there is a comment suggesting that ISMINE_ALL should never be cached.
It seems it's just for efficiency, as in bitcoin it's just

ISMINE_ALL = ISMINE_WATCH_ONLY | ISMINE_SPENDABLE

and ISMINE_WATCH_ONLY/ISMINE_SPENDABLE types do not overlap (an output cannot be both at the same time).
But in our case it's different.
ISMINE_SPENDABLE_STAKEABLE (which is not a "power-of-2" element of isminetype) can be returned by IsMine() .

Alternatively, for now, we can just add some condition/hack for this particular situation.

But in any case, in the future, we should properly reorganize this, as it seems highly inefficient as it is: we keep a vector of 16 cacheable amounts, to actually use only 4 of them. In upstream this is less relevant (they use 2 out of 4).

@random-zebra
Copy link

Thinking more about it, for the problem explained above, we are forced to actually use the ISMINE_SPENDABLE_STAKEABLE (separate) cached amount. There is no way to distinguish the cold staking types otherwise. So, I guess, we should do something like this:

CAmount CWalletTx::GetCredit(const isminefilter& filter, bool recalculate, bool fUnspent) const
{
    CAmount credit = 0;
    if (filter & ISMINE_SPENDABLE) {
        // GetBalance can assume transactions in mapWallet won't change
        credit += GetCachableAmount(CREDIT, ISMINE_SPENDABLE, recalculate, fUnspent);
    }
    if (filter & ISMINE_WATCH_ONLY) {
        credit += GetCachableAmount(CREDIT, ISMINE_WATCH_ONLY, recalculate, fUnspent);
    }

    bool fOnlyCold = (filter & ISMINE_COLD) && !(filter & ISMINE_SPENDABLE_DELEGATED);
    bool fOnlyDelegated = !(filter & ISMINE_COLD) && (filter & ISMINE_SPENDABLE_DELEGATED);
    if (fOnlyCold) {
        credit += GetCachableAmount(CREDIT, ISMINE_COLD, recalculate, fUnspent);
    } else if (fOnlyDelegated) {
        credit += GetCachableAmount(CREDIT, ISMINE_SPENDABLE_DELEGATED, recalculate, fUnspent);
    } else if (filter & ISMINE_SPENDABLE_STAKEABLE)  {
        // cached mixed balance (sum of outputs that are either COLD, or SPENDABLE_DELEGATED,
        // counting them only once if the wallet has both coldstaking keys).
        credit += GetCachableAmount(CREDIT, ISMINE_SPENDABLE_STAKEABLE, recalculate, fUnspent);
    }
    return credit;
}

In the future, would be better to refactor m_amounts, giving up on the simplicity of the direct mapping, and using a vector of only 5 cached amounts.

@furszy
Copy link
Author

furszy commented Aug 19, 2020

Good catch zebra :) , cool that this work popped up few hidden issues and good discussions.

Have something in mind, do we really need the ISMINE_SPENDABLE_DELEGATED type at all?. It seems to be just a redundant representation of a ISMINE_SPENDABLE sub type that can be simplified.

When the wallet wants to check any output credit, it already know whether is a P2CS or not. If the P2CS is spendable then it's a ISMINE_SPENDABLE (the wallet has the owner priv key), if not then is ISMINE_COLD or a ISMINE_WATCH_ONLY.

Going more general to describe this a little bit better. IsMine() shouldn't return the type of the output script (can already be done outside of the function). It should purely return the wallet spending authority type.
Example:

if (output.IsPayToColdStaking() && IsMine(output) == ISMINE_SPENDABLE) {
 // we know here that the output is a `ISMINE_SPENDABLE_DELEGATED`
 // without having the extra enum type and any other logic complication.
}

Doing this structural modification, we would be erasing the problem's root and making the flow simpler.

@random-zebra
Copy link

random-zebra commented Aug 19, 2020

Yes, we can do that, but then you would lose the mapping between cached amounts and ismine types.
And it wouldn't remove the problem: if you want to cache delegated/cold balance per-tx, for efficiency, you need to consider the situation of having both keys (so, an additional cached amount).

What we could do, though, is return ISMINE_SPENDABLE_DELEGATED also in the case of a wallet with both keys. This would change slightly the semantic of "cold balance" (as it would not include self-delegated outputs anymore) but it would remove the problem of having "overlapping" cached amounts: an output could be SPENDABLE_DELEGATED or COLD, but not both.

Merging SPENDABLE_DELEGATED with SPENDABLE, as you suggest, would be an additional step, but then, again, we would lose the mapping (and I think it's convenient to have a separate cached balance just for delegations).

Anyway, this kind of refactoring would require its own PR, so for now we can either add a comment in GetCredit/GetDebit, or maybe temporarily use an hack like the one I suggested above.

@furszy
Copy link
Author

furszy commented Aug 19, 2020

loose the one-to-one isminetype -> value cache mapping is good actually. As you said in the first comment, we are having there more balance types than what we are really using. We just need to store the basic types there, not all the possible combinations (those would be duplicating amounts, adding few other values already cached into other cached space).

ofc not planning to do it here. Just continuing the nice convo where we started it :) .
Will add the quick hack to continue moving on and we can back to this talk after #1810 (which adds the test coverage for the cache balances).

@random-zebra
Copy link

loose the one-to-one isminetype -> value cache mapping is good actually.

I agree.

@furszy
Copy link
Author

furszy commented Aug 19, 2020

Added a commit following up the convo and zebra's last suggestion.
ISMINE_SPENDABLE_DELEGATED will be now taking precedence over ISMINE_COLD, simple and clean solution.
Ready for review.

@random-zebra
Copy link

wallet_import_stakingaddress needs to be updated for the semantic change to cold balance (node 0 does self-delegations at the beginning). We should check the delegated balance on it now (line 50), instead of the cold staking balance.

ISMINE_SPENDABLE_DELEGATED will be taking precedence over ISMINE_COLD if the wallet has both priv keys.
@furszy furszy force-pushed the 2020_abstract_out_isSolvable branch from 147ae4b to e02edd3 Compare August 20, 2020 15:03
@furszy
Copy link
Author

furszy commented Aug 20, 2020

Done 👍

Copy link

@random-zebra random-zebra left a comment

Choose a reason for hiding this comment

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

utACK e02edd3

@furszy furszy merged commit e2cc4aa into PIVX-Project:master Aug 21, 2020
@random-zebra random-zebra modified the milestones: 5.0.0, 4.3.0 Sep 10, 2020
furszy added a commit that referenced this pull request Oct 4, 2020
…vable

b425466 [Script] Add fColdStaking bool to IsSolvable (random-zebra)

Pull request description:

  Fixing a minor bug introduced in #1757.
  `IsSolvable` always tries to produce a signature with the owner key, so it logs an error (and reports the coin as not solvable) for cold stakers.
  Fix it introducing a boolean argument in `IsSolvable`, to check the appropriate public key in the keystore.

ACKs for top commit:
  furszy:
    Great catch 👌 , ACK b425466
  Fuzzbawls:
    ACK b425466

Tree-SHA512: 9d4537218344c8176f1de500664905e78419fd852c9607b40cf83eedb55e5fb965977f31bba0a605fac35c9bee373613467d63c6d3f72386c598e7452b545101
@furszy furszy deleted the 2020_abstract_out_isSolvable branch November 29, 2022 14:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants