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

refactor: Make CWalletTx sync state type-safe #21206

Merged
merged 1 commit into from Nov 25, 2021

Conversation

ryanofsky
Copy link
Contributor

@ryanofsky ryanofsky commented Feb 17, 2021

Current CWalletTx state representation makes it possible to set inconsistent states that won't be handled correctly by wallet sync code or serialized & deserialized back into the same form.

For example, it is possible to call setConflicted without setting a conflicting block hash, or setConfirmed with no transaction index. And it's possible update individual m_confirm and fInMempool data fields without setting an overall consistent state that can be serialized and handled correctly.

Fix this without changing behavior by using std::variant, instead of an enum and collection of fields, to represent sync state, so state tracking code is safer and more legible.

This is a first step to fixing state tracking bugs https://github.com/bitcoin-core/bitcoin-devwiki/wiki/Wallet-Transaction-Conflict-Tracking, by adding an extra margin of safety that can prevent new bugs from being introduced as existing bugs are fixed.

ryanofsky added a commit to ryanofsky/bitcoin that referenced this pull request Feb 17, 2021
This commit just moves code without making any changes. It can be
reviewed with `git log -p -n1 --color-moved=dimmed_zebra`

Motivation for this change is to make wallet.cpp/h less monolithic and
start to make wallet transaction state tracking comprehensible so bugs
in
https://github.com/bitcoin-core/bitcoin-devwiki/wiki/Wallet-Transaction-Conflict-Tracking
can be fixed safely without introducing new problems.

This commit moves wallet classes and methods that deal with transactions
out of wallet.cpp/.h into better organized files:

- transaction.cpp/.h - CWalletTx and CMerkleTX class definitions
- receive.cpp/.h - functions checking received transactions and computing balances
- spend.cpp/.h - functions creating transactions and finding spendable coins

After bitcoin#20773, when loading is separated from syncing it will also be
possible to move more wallet.cpp/.h functions to:

- sync.cpp/.h - functions handling chain notifications and rescanning

This commit arranges receive.cpp and spend.cpp functions in dependency
order so it's possible to skim receive.cpp and get an idea of how
computing balances works, and skim spend.cpp and get an idea of how
transactions are created, without having to jump all over wallet.cpp
where functions are not in order and there is a lot of unrelated
uncode.

Followup commit "refactor: Detach wallet transaction methods" in
bitcoin#21206 follows up this PR and
tweaks function names and arguments to reflect new locations. The two
commits are split into separate PRs because this commit is more work to
maintain and less work to review, while the other commit is less work to
maintain and more work to review, so hopefully this commit can be merged
earlier.
@DrahtBot
Copy link
Contributor

DrahtBot commented Feb 17, 2021

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

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #23497 (Add src/node/ and src/wallet/ code to node:: and wallet:: namespaces by ryanofsky)
  • #23411 (refactor: Avoid integer overflow in ApplyStats when activating snapshot by MarcoFalke)

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.

This was referenced Feb 17, 2021
Copy link
Member

@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.

Approach ACK (also base PR).

This makes it a lot easier to reason about transaction state. Another great achievement is dropping wallet dependency from transaction. 👏

src/wallet/transaction.h Outdated Show resolved Hide resolved
ryanofsky added a commit to ryanofsky/bitcoin that referenced this pull request Mar 4, 2021
This commit just moves code without making any changes. It can be
reviewed with `git log -p -n1 --color-moved=dimmed_zebra`

Motivation for this change is to make wallet.cpp/h less monolithic and
start to make wallet transaction state tracking comprehensible so bugs
in
https://github.com/bitcoin-core/bitcoin-devwiki/wiki/Wallet-Transaction-Conflict-Tracking
can be fixed safely without introducing new problems.

This commit moves wallet classes and methods that deal with transactions
out of wallet.cpp/.h into better organized files:

- transaction.cpp/.h - CWalletTx and CMerkleTX class definitions
- receive.cpp/.h - functions checking received transactions and computing balances
- spend.cpp/.h - functions creating transactions and finding spendable coins

After bitcoin#20773, when loading is separated from syncing it will also be
possible to move more wallet.cpp/.h functions to:

- sync.cpp/.h - functions handling chain notifications and rescanning

This commit arranges receive.cpp and spend.cpp functions in dependency
order so it's possible to skim receive.cpp and get an idea of how
computing balances works, and skim spend.cpp and get an idea of how
transactions are created, without having to jump all over wallet.cpp
where functions are not in order and there is a lot of unrelated
uncode.

Followup commit "refactor: Detach wallet transaction methods" in
bitcoin#21206 follows up this PR and
tweaks function names and arguments to reflect new locations. The two
commits are split into separate PRs because this commit is more work to
maintain and less work to review, while the other commit is less work to
maintain and more work to review, so hopefully this commit can be merged
earlier.
ryanofsky added a commit to ryanofsky/bitcoin that referenced this pull request Mar 4, 2021
This commit just moves functions without making any changes. It can be
reviewed with `git log -p -n1 --color-moved=dimmed_zebra`

Motivation for this change is to make wallet.cpp/h less monolithic and
start to make wallet transaction state tracking comprehensible so bugs
in
https://github.com/bitcoin-core/bitcoin-devwiki/wiki/Wallet-Transaction-Conflict-Tracking
can be fixed safely without introducing new problems.

This commit moves wallet classes and methods that deal with transactions
out of wallet.cpp/.h into better organized files:

- transaction.cpp/.h - CWalletTx and CMerkleTx class definitions
- receive.cpp/.h - functions checking received transactions and computing balances
- spend.cpp/.h - functions creating transactions and finding spendable coins

After bitcoin#20773, when loading is separated from syncing it will also be
possible to move more wallet.cpp/.h functions to:

- sync.cpp/.h - functions handling chain notifications and rescanning

This commit arranges receive.cpp and spend.cpp functions in dependency
order so it's possible to skim receive.cpp and get an idea of how
computing balances works, and skim spend.cpp and get an idea of how
transactions are created, without having to jump all over wallet.cpp
where functions are not in order and there is a lot of unrelated code.

Followup commit "refactor: Detach wallet transaction methods" in
bitcoin#21206 follows up this PR and
tweaks function names and arguments to reflect new locations. The two
commits are split into separate PRs because this commit is more work to
maintain and less work to review, while the other commit is less work to
maintain and more work to review, so hopefully this commit can be merged
earlier.
Current CWalletTx state representation makes it possible to set
inconsistent states that won't be handled correctly by wallet sync code
or serialized & deserialized back into the same form.

For example, it is possible to call setConflicted without setting a
conflicting block hash, or setConfirmed with no transaction index. And
it's possible update individual m_confirm and fInMempool data fields
without setting an overall consistent state that can be serialized and
handled correctly.

Fix this without changing behavior by using std::variant, instead of an
enum and collection of fields, to represent sync state, so state
tracking code is safer and more legible.

This is a first step to fixing state tracking bugs
https://github.com/bitcoin-core/bitcoin-devwiki/wiki/Wallet-Transaction-Conflict-Tracking,
by adding an extra margin of safety that can prevent new bugs from being
introduced as existing bugs are fixed.
@ryanofsky
Copy link
Contributor Author

Rebased 3c6fdae -> 78c8ac8 (pr/txstate.24 -> pr/txstate.25, compare) due to conflict with #22928, also making minor MakeWalletTxStatus cleanup

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.

re-utACK 78c8ac8

@jonatack
Copy link
Contributor

Concept ACK, wil review today.

@laanwj
Copy link
Member

laanwj commented Nov 23, 2021

I like this refactor, it abstracts the transaction state much better than the boolean-radio-buttons, and agree it makes some kinds of mistake harder to make.

Concept and code review ACK 78c8ac8
re-ACK d8ee8f3

Copy link
Member

@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.

Code review ACK 78c8ac8.

In a follow-up, we can consolidate updates to m_state, for instance, the 2nd update can be avoided:

transactionAddedToMempool
    SyncTransaction
        AddToWalletIfInvolvingMe
            AddToWallet
                m_state = ...
    RefreshMempoolStatus
        m_state = ...

Copy link
Contributor

@jonatack jonatack left a comment

Choose a reason for hiding this comment

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

Approach ACK / light code review ACK 78c8ac8

src/wallet/rpcdump.cpp Outdated Show resolved Hide resolved
src/wallet/wallet.h Show resolved Hide resolved
src/wallet/wallet.cpp Outdated Show resolved Hide resolved
src/wallet/transaction.h Show resolved Hide resolved
src/wallet/transaction.h Show resolved Hide resolved
src/wallet/transaction.h Show resolved Hide resolved
src/util/overloaded.h Outdated Show resolved Hide resolved
Copy link
Contributor Author

@ryanofsky ryanofsky left a comment

Choose a reason for hiding this comment

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

Thanks for reviews and suggestions!

Updated 78c8ac8 -> d8ee8f3 (pr/txstate.25 -> pr/txstate.26, compare)

src/util/overloaded.h Outdated Show resolved Hide resolved
src/wallet/rpcdump.cpp Outdated Show resolved Hide resolved
src/wallet/transaction.h Show resolved Hide resolved
src/wallet/transaction.h Show resolved Hide resolved
src/wallet/transaction.h Show resolved Hide resolved
src/wallet/wallet.cpp Show resolved Hide resolved
src/wallet/wallet.cpp Outdated Show resolved Hide resolved
src/wallet/wallet.h Show resolved Hide resolved
// is needed before confirm again with different block.
return wallet.AddToWallet(MakeTransactionRef(tx), confirm, [&](CWalletTx& wtx, bool /* new_tx */) {
wtx.setUnconfirmed();
return wallet.AddToWallet(MakeTransactionRef(tx), state, [&](CWalletTx& wtx, bool /* new_tx */) {
Copy link
Contributor

Choose a reason for hiding this comment

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

(if need to retouch)

Suggested change
return wallet.AddToWallet(MakeTransactionRef(tx), state, [&](CWalletTx& wtx, bool /* new_tx */) {
return wallet.AddToWallet(MakeTransactionRef(tx), state, [&](CWalletTx& wtx, /*new_tx=*/bool) {

// serialized transaction.
wtx.m_confirm.block_height = height;
} else if (wtx.isConflicted() || wtx.isConfirmed()) {
auto lookup_block = [&](const uint256& hash, int& height, TxState& state) {
// If tx block (or conflicting block) was reorged out of chain
// while the wallet was shutdown, change tx status to UNCONFIRMED
// and reset block height, hash, and index. ABANDONED tx don't have
Copy link
Contributor

Choose a reason for hiding this comment

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

Should the documentation be updated here as well?

src/wallet/wallet.cpp Show resolved Hide resolved
src/wallet/rpcdump.cpp Outdated Show resolved Hide resolved
Copy link
Contributor

@jonatack jonatack 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 d8ee8f3

@laanwj laanwj removed this from Blockers in High-priority for review Nov 25, 2021
@laanwj laanwj merged commit cf24152 into bitcoin:master Nov 25, 2021
sidhujag pushed a commit to syscoin/syscoin that referenced this pull request Nov 26, 2021
@bitcoin bitcoin locked and limited conversation to collaborators Apr 11, 2023
//! https://en.cppreference.com/w/cpp/utility/variant/visit#Example
template<class... Ts> struct Overloaded : Ts... { using Ts::operator()...; };

//! Explicit deduction guide (not needed as of C++20)
Copy link
Member

Choose a reason for hiding this comment

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

As for Clang, this explicit deduction guide can be dropped for versions >=17 only, unfortunately.

Copy link
Member

Choose a reason for hiding this comment

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

This is part of #29042.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

10 participants