Skip to content

Conversation

fjahr
Copy link
Contributor

@fjahr fjahr commented Jan 2, 2020

Fixes #17603 (together with #17824)

getbalances is using the cache within GetAvailableCredit under certain conditions here. For a wallet with avoid_reuse activated this can lead to inconsistent reporting of used transactions/balances between getbalances and listunspent as pointed out in #17603. When an address is reused before the first transaction is spending from this address, the cache is not updated even after the transaction is sent. This means the remaining outputs at the reused address are not showing up as used in getbalances.

With this change, any newly incoming transaction belonging to the wallet marks all the other outputs at the same address as dirty.

@DrahtBot
Copy link
Contributor

DrahtBot commented Jan 2, 2020

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #17889 (wallet: Improve CWallet:MarkDestinationsDirty by promag)
  • #17838 (test: test the >10 UTXO case for output groups by kallewoof)
  • #17824 (wallet: Improve coin selection for destination groups >10 by fjahr)
  • #9381 (Remove CWalletTx merging logic from AddToWallet by ryanofsky)

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.

Copy link
Contributor

@promag promag left a comment

Choose a reason for hiding this comment

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

Concept ACK.

if (AddDestData(batch, dst, "used", "p")) {
// mark all other outputs to the same destination as dirty
// so they pick up the new "used" state as quickly as possible
MarkDestinationTransactionsDirty(dst);
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like we should have std::multimap<CTxDestination, CWalletTx*>?

Copy link
Member

@instagibbs instagibbs left a comment

Choose a reason for hiding this comment

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

concept ACK

Copy link
Contributor

@kallewoof kallewoof left a comment

Choose a reason for hiding this comment

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

LGTM with a few nits

@kallewoof
Copy link
Contributor

Github spazzing out, but #17843 (comment) is in response to @instagibbs suggestion to break. I think breaking would skip over any remaining UTXO:s sent to the same address.

Copy link
Contributor

@promag promag left a comment

Choose a reason for hiding this comment

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

I think current approach is really bad - SetUsedDestinationState is called for each spent TXO which in turn will iterate all wallet transaction outputs.

I see 2 options to improve:

  • have an index like std::multimap<CTxDestination, CWalletTx*>
  • accumulate all CTxDestination to be marked dirty and then iterate the wallet transactions just once.

@kallewoof
Copy link
Contributor

kallewoof commented Jan 3, 2020

@promag You may be right. Would love to see worst case scenario analysis (is this O(n) or O(n^2) or worse?).

Copy link
Contributor

@promag promag left a comment

Choose a reason for hiding this comment

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

@kallewoof I think it's O(n). Problem is that for big wallets, all transaction outputs will be scanned as much as the input count of the new transaction.

@kallewoof
Copy link
Contributor

@kallewoof I think it's O(n). Problem is that for big wallets, all transaction outputs will be scanned as much as the input count of the new transaction.

So if n is wallet size and m is input count, this would be O(nm). That's pretty bad, but honestly don't think it's a huge deal. I could be wrong, though.

@instagibbs
Copy link
Member

This might make a good topic for IRC meeting. I think making this slower is ok provided it's opt-in, which it appears to be based on the required avoid_reuse wallet flag guarding SetUsedDestinationState. An algorithmic cleanup is of course encouraged.

@achow101
Copy link
Member

achow101 commented Jan 3, 2020

I agree with @promag. I think we should use std::unordered_multimap and accumulate the destinations so that it can all be done with one iteration over the fewest CWalletTxs needed. O(nm) can be pretty slow. We already know that for large wallets, things that do this kind of iteration can take a while so I think we should try to avoid further iteration like this, especially in something that will be called as frequently as every incoming transaction.

@promag
Copy link
Contributor

promag commented Jan 3, 2020

I also think it's fine to improve in a follow up and have the fix merged. In that case I'd just change this to do as suggested:

accumulate all CTxDestination to be marked dirty and then iterate the wallet transactions just once.

which is pretty simple.

@fjahr
Copy link
Contributor Author

fjahr commented Jan 3, 2020

Thanks for the feedback everyone! I can improve the PR as suggested by @promag shortly, no need to have a separate follow-up for that. But a further improvement using an index could be a follow-up.

@fjahr
Copy link
Contributor Author

fjahr commented Jan 6, 2020

Pushed update including most of the feedback and implementing the simpler solution suggestion by @promag:

  • accumulate all CTxDestination to be marked dirty and then iterate the wallet transactions just once.

Copy link
Member

@achow101 achow101 left a comment

Choose a reason for hiding this comment

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

ACK 88291a5

@fjahr
Copy link
Contributor Author

fjahr commented Jan 8, 2020

Rebased and addressed feedback.

Copy link
Contributor

@kallewoof kallewoof left a comment

Choose a reason for hiding this comment

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

Code reviewed, looks good aside from outdated comment.

If a destination is reused we mark the cache of the other transactions going to that destination dirty so they are not accidentally reported as trusted when the cache is hit.
@fjahr
Copy link
Contributor Author

fjahr commented Jan 13, 2020

I did some rough benchmarks to see how this would affect biggish wallets, and am not seeing any significant drops in performance:

Thanks a lot for running these!

Pushed update to outdated comment.

Copy link
Contributor

@promag promag left a comment

Choose a reason for hiding this comment

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

ACK 6fc554f.

@kallewoof
Copy link
Contributor

Code review re-ACK 6fc554f

Copy link
Member

@achow101 achow101 left a comment

Choose a reason for hiding this comment

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

Re-ACK 6fc554f

Copy link
Contributor

@meshcollider meshcollider left a comment

Choose a reason for hiding this comment

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

Code review ACK 6fc554f

Thanks!

meshcollider added a commit that referenced this pull request Jan 15, 2020
6fc554f wallet: Reset reused transactions cache (Fabian Jahr)

Pull request description:

  Fixes #17603 (together with #17824)

  `getbalances` is using the cache within `GetAvailableCredit` under certain conditions [here](https://github.com/bitcoin/bitcoin/blob/35fff5be60e853455abc24713481544e91adfedb/src/wallet/wallet.cpp#L1826). For a wallet with `avoid_reuse` activated this can lead to inconsistent reporting of `used` transactions/balances between `getbalances` and `listunspent` as pointed out in #17603. When an address is reused before the first transaction is spending from this address, the cache is not updated even after the transaction is sent. This means the remaining outputs at the reused address are not showing up as `used` in `getbalances`.

  With this change, any newly incoming transaction belonging to the wallet marks all the other outputs at the same address as dirty.

ACKs for top commit:
  kallewoof:
    Code review re-ACK 6fc554f
  promag:
    ACK 6fc554f.
  achow101:
    Re-ACK 6fc554f
  meshcollider:
    Code review ACK 6fc554f

Tree-SHA512: c4cad2c752176d16d77b4a4202291d20baddf9f27250896a40274d74a6945e0f6b34be04c2f9b1b2e756d3ac669b794969df8f82a98e0b16f10e92f276649ea2
@meshcollider meshcollider merged commit 6fc554f into bitcoin:master Jan 15, 2020
sidhujag pushed a commit to syscoin/syscoin that referenced this pull request Jan 15, 2020
6fc554f wallet: Reset reused transactions cache (Fabian Jahr)

Pull request description:

  Fixes bitcoin#17603 (together with bitcoin#17824)

  `getbalances` is using the cache within `GetAvailableCredit` under certain conditions [here](https://github.com/bitcoin/bitcoin/blob/35fff5be60e853455abc24713481544e91adfedb/src/wallet/wallet.cpp#L1826). For a wallet with `avoid_reuse` activated this can lead to inconsistent reporting of `used` transactions/balances between `getbalances` and `listunspent` as pointed out in bitcoin#17603. When an address is reused before the first transaction is spending from this address, the cache is not updated even after the transaction is sent. This means the remaining outputs at the reused address are not showing up as `used` in `getbalances`.

  With this change, any newly incoming transaction belonging to the wallet marks all the other outputs at the same address as dirty.

ACKs for top commit:
  kallewoof:
    Code review re-ACK 6fc554f
  promag:
    ACK 6fc554f.
  achow101:
    Re-ACK 6fc554f
  meshcollider:
    Code review ACK 6fc554f

Tree-SHA512: c4cad2c752176d16d77b4a4202291d20baddf9f27250896a40274d74a6945e0f6b34be04c2f9b1b2e756d3ac669b794969df8f82a98e0b16f10e92f276649ea2
@fanquake
Copy link
Member

@meshcollider does this need backporting to 0.19?

luke-jr pushed a commit to luke-jr/bitcoin that referenced this pull request Feb 6, 2020
If a destination is reused we mark the cache of the other transactions going to that destination dirty so they are not accidentally reported as trusted when the cache is hit.

Github-Pull: bitcoin#17843
Rebased-From: 6fc554f
@fanquake
Copy link
Member

fanquake commented Feb 6, 2020

Being backported in #18083.

laanwj added a commit that referenced this pull request Feb 10, 2020
f11872c wallet: Reset reused transactions cache (Fabian Jahr)

Pull request description:

  Backport of #17843

  Required porting to pre-`WalletBatch`

ACKs for top commit:
  kallewoof:
    Code review ACK f11872c
  laanwj:
    code review ACK f11872c
  meshcollider:
    utACK f11872c

Tree-SHA512: 5cf5f136d1eafb0783c2e6799e3675ebc50997ebb56b379d8a198ac35eb3b32f6b98656760a8b1c821eeac665eb80efb1723dd4e9eb58d2b4d45c4674499bedf
MarkLTZ added a commit to litecoinz-core/litecoinz that referenced this pull request Mar 13, 2020
[0.19] Backports bitcoin#17858
Unbreak build with Boost 1.72.0 bitcoin#17654
cli: fix Fatal LevelDB error when specifying -blockfilterindex=basic twice bitcoin#17687
rpc: require second argument only for scantxoutset start action bitcoin#17728
wallet: Fix origfee return for bumpfee with feerate arg bitcoin#17643
test: fix "bitcoind already running" warnings on macOS bitcoin#17488
net: Log to net category for exceptions in ProcessMessages bitcoin#17762
Updates to appveyor config for VS2019 and Qt5.9.8 + msvc project fixes bitcoin#17364
Appveyor improvement - text file for vcpkg package list bitcoin#17416
Update msvc build for Visual Studio 2019 v16.4 bitcoin#17736
scripts: fix symbol-check & security-check argument passing bitcoin#17857
qt: Periodic translations update for 0.19 branch
IsUsedDestination should count any known single-key address bitcoin#17621
init: Stop indexes on shutdown after ChainStateFlushed callback. bitcoin#17897
qt: Translations update pre-rc1
wallet: Reset reused transactions cache bitcoin#17843
Squashed 'src/univalue/' changes from 7890db9..98261b1
0.19: Update univalue subtree bitcoin#18100
qt: Pre-rc2 translations update
MarkLTZ added a commit to litecoinz-core/litecoinz that referenced this pull request Mar 13, 2020
[0.19] Backports bitcoin#17858
Unbreak build with Boost 1.72.0 bitcoin#17654
cli: fix Fatal LevelDB error when specifying -blockfilterindex=basic twice bitcoin#17687
rpc: require second argument only for scantxoutset start action bitcoin#17728
wallet: Fix origfee return for bumpfee with feerate arg bitcoin#17643
test: fix "bitcoind already running" warnings on macOS bitcoin#17488
net: Log to net category for exceptions in ProcessMessages bitcoin#17762
Updates to appveyor config for VS2019 and Qt5.9.8 + msvc project fixes bitcoin#17364
Appveyor improvement - text file for vcpkg package list bitcoin#17416
Update msvc build for Visual Studio 2019 v16.4 bitcoin#17736
scripts: fix symbol-check & security-check argument passing bitcoin#17857
qt: Periodic translations update for 0.19 branch
IsUsedDestination should count any known single-key address bitcoin#17621
init: Stop indexes on shutdown after ChainStateFlushed callback. bitcoin#17897
qt: Translations update pre-rc1
wallet: Reset reused transactions cache bitcoin#17843
Squashed 'src/univalue/' changes from 7890db9..98261b1
0.19: Update univalue subtree bitcoin#18100
qt: Pre-rc2 translations update
[0.19] Further 0.19 backports bitcoin#18218
build: don't embed a build-id when building libdmg-hfsplus bitcoin#18004
meshcollider added a commit that referenced this pull request Apr 17, 2020
a2324e4 test: Improve naming and logging of avoid_reuse tests (Fabian Jahr)
1abbdac wallet: Prefer full destination groups in coin selection (Fabian Jahr)

Pull request description:

  Fixes #17603 (together with #17843)

  In the case of destination groups of >10 outputs existing in a wallet with `avoid_reuse` enabled, the grouping algorithm is adding left-over outputs as an "incomplete" group to the list of groups even when a full group has already been added. This leads to the strange behavior that if there are >10 outputs for a destination the transaction spending from that will effectively use `len(outputs) % 10` as inputs for that transaction.

  From the original PR and the code comment I understand the correct behavior should be the usage of 10 outputs. I opted for minimal changes in the current code although there maybe optimizations possible for cases with >20 outputs on a destination this sounds like too much of an edge case right now.

ACKs for top commit:
  jonatack:
    Re-ACK a2324e4
  achow101:
    ACK a2324e4
  kallewoof:
    ACK a2324e4
  meshcollider:
    Tested ACK a2324e4 (verified the new test fails on master without this change)

Tree-SHA512: 4743779c5d469fcd16df5baf166024b1d3c8eaca151df1e8281b71df62b29541cf7bfee3f8ab48d83e3b34c9256e53fd38a7b146a54c79f9caa44cce3636971a
sidhujag pushed a commit to syscoin/syscoin that referenced this pull request Apr 17, 2020
…election

a2324e4 test: Improve naming and logging of avoid_reuse tests (Fabian Jahr)
1abbdac wallet: Prefer full destination groups in coin selection (Fabian Jahr)

Pull request description:

  Fixes bitcoin#17603 (together with bitcoin#17843)

  In the case of destination groups of >10 outputs existing in a wallet with `avoid_reuse` enabled, the grouping algorithm is adding left-over outputs as an "incomplete" group to the list of groups even when a full group has already been added. This leads to the strange behavior that if there are >10 outputs for a destination the transaction spending from that will effectively use `len(outputs) % 10` as inputs for that transaction.

  From the original PR and the code comment I understand the correct behavior should be the usage of 10 outputs. I opted for minimal changes in the current code although there maybe optimizations possible for cases with >20 outputs on a destination this sounds like too much of an edge case right now.

ACKs for top commit:
  jonatack:
    Re-ACK a2324e4
  achow101:
    ACK a2324e4
  kallewoof:
    ACK a2324e4
  meshcollider:
    Tested ACK a2324e4 (verified the new test fails on master without this change)

Tree-SHA512: 4743779c5d469fcd16df5baf166024b1d3c8eaca151df1e8281b71df62b29541cf7bfee3f8ab48d83e3b34c9256e53fd38a7b146a54c79f9caa44cce3636971a
jasonbcox pushed a commit to Bitcoin-ABC/bitcoin-abc that referenced this pull request Oct 7, 2020
Summary:
If a destination is reused we mark the cache of the other transactions going to that destination dirty so they are not accidentally reported as trusted when the cache is hit.

---

Backport of Core [[bitcoin/bitcoin#17843 | PR17843]]

Test Plan:
  ninja check check-functional

Reviewers: #bitcoin_abc, deadalnix

Reviewed By: #bitcoin_abc, deadalnix

Differential Revision: https://reviews.bitcoinabc.org/D7685
sidhujag pushed a commit to syscoin-core/syscoin that referenced this pull request Nov 10, 2020
6fc554f wallet: Reset reused transactions cache (Fabian Jahr)

Pull request description:

  Fixes bitcoin#17603 (together with bitcoin#17824)

  `getbalances` is using the cache within `GetAvailableCredit` under certain conditions [here](https://github.com/bitcoin/bitcoin/blob/35fff5be60e853455abc24713481544e91adfedb/src/wallet/wallet.cpp#L1826). For a wallet with `avoid_reuse` activated this can lead to inconsistent reporting of `used` transactions/balances between `getbalances` and `listunspent` as pointed out in bitcoin#17603. When an address is reused before the first transaction is spending from this address, the cache is not updated even after the transaction is sent. This means the remaining outputs at the reused address are not showing up as `used` in `getbalances`.

  With this change, any newly incoming transaction belonging to the wallet marks all the other outputs at the same address as dirty.

ACKs for top commit:
  kallewoof:
    Code review re-ACK 6fc554f
  promag:
    ACK 6fc554f.
  achow101:
    Re-ACK 6fc554f
  meshcollider:
    Code review ACK 6fc554f

Tree-SHA512: c4cad2c752176d16d77b4a4202291d20baddf9f27250896a40274d74a6945e0f6b34be04c2f9b1b2e756d3ac669b794969df8f82a98e0b16f10e92f276649ea2
Munkybooty pushed a commit to Munkybooty/dash that referenced this pull request Dec 9, 2021
f11872c wallet: Reset reused transactions cache (Fabian Jahr)

Pull request description:

  Backport of bitcoin#17843

  Required porting to pre-`WalletBatch`

ACKs for top commit:
  kallewoof:
    Code review ACK f11872c
  laanwj:
    code review ACK f11872c
  meshcollider:
    utACK f11872c

Tree-SHA512: 5cf5f136d1eafb0783c2e6799e3675ebc50997ebb56b379d8a198ac35eb3b32f6b98656760a8b1c821eeac665eb80efb1723dd4e9eb58d2b4d45c4674499bedf
@bitcoin bitcoin locked as resolved and limited conversation to collaborators Feb 15, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

partial spend avoidance makes partial spends and getbalances doesn't notice
8 participants