Skip to content

refactor: remove ::vpwallets and related global variables#19101

Merged
fanquake merged 1 commit intobitcoin:masterfrom
ryanofsky:pr/novp
Aug 19, 2021
Merged

refactor: remove ::vpwallets and related global variables#19101
fanquake merged 1 commit intobitcoin:masterfrom
ryanofsky:pr/novp

Conversation

@ryanofsky
Copy link
Contributor

@ryanofsky ryanofsky commented May 28, 2020

Get rid of global wallet list variables by moving them to WalletContext struct

@DrahtBot
Copy link
Contributor

DrahtBot commented May 29, 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:

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.

ryanofsky added a commit to ryanofsky/bitcoin that referenced this pull request May 29, 2020
Qt tests currently are currently using two NodeContext structs at the same
time, one in interfaces::NodeImpl::m_context, and the other in
BasicTestingSetup::m_node, and the tests have hacks transferring state between
them.

Fix this by getting rid of the NodeImpl::m_context struct and making it a
pointer. This way a common BitcoinApplication object can be used for all qt
tests, but they can still have their own testing setups.

Non-test code is changing but non-test behavior is still the same as before.

Benefit of this PR is to being able to remove the existing
"std::move(test.m_node.connman)" and mempool hacks.

Motivation for this PR is to let qt wallet tests continue working when the
::vpwallets global variable is removed bitcoin#19101 and not have to add even more
complicated hacks exposing duplicate WalletContext and WalletClient instances
in addition to the duplicate NodeContext instances and having to manipulate
them in the test setup.
@ryanofsky
Copy link
Contributor Author

ryanofsky commented May 29, 2020

Rebased 197a403 -> 7272686 (pr/novp.1 -> pr/novp.2, compare) with updated base prs and fixed walletcontroller lifetime bug in test
Rebased 7272686 -> 924561d (pr/novp.2 -> pr/novp.3, compare) to fix UndefinedBehaviorSanitizer error https://travis-ci.org/github/bitcoin/bitcoin/jobs/692548214#L4281 from #19098, also making changes to fix disable wallet build errors https://travis-ci.org/github/bitcoin/bitcoin/jobs/692548213#L3864 and importwallet_rescan segfault https://travis-ci.org/github/bitcoin/bitcoin/jobs/692548218#L10769
Rebased 924561d -> f2d9af6 (pr/novp.3 -> pr/novp.4, compare) with no changes to avoid travis ARM buster "sudo: unable to resolve host travis-job-bitcoin-bitcoin-69472326" error https://travis-ci.org/github/bitcoin/bitcoin/jobs/694723263 reported in #19171
Rebased f2d9af6 -> de89a09 (pr/novp.4 -> pr/novp.5, compare) with no changes after base pr #19096 merged
Rebased de89a09 -> e805b16 (pr/novp.5 -> pr/novp.6, compare) on rebased base pr #19099 pr/wclient.4
Rebased e805b16 -> 0b60ae5 (pr/novp.6 -> pr/novp.7, compare) due to conflicts with #19250 and #19261 on rebased base pr #19099 pr/wclient.5
Rebased 0b60ae5 -> 6c9a117 (pr/novp.7 -> pr/novp.8, compare) due to conflicts with #19300
Rebased 6c9a117 -> d22a053 (pr/novp.8 -> pr/novp.9, compare) on top of #19099 pr/wclient.6
Rebased d22a053 -> 6f799b1 (pr/novp.9 -> pr/novp.10, compare) on top of #19099 pr/wclient.7
Rebased 6f799b1 -> 58c9550 (pr/novp.10 -> pr/novp.11, compare) due to conflict with #19334
Rebased 58c9550 -> a00fc53 (pr/novp.11 -> pr/novp.12, compare) on top of #19099 pr/wclient.8
Rebased a00fc53 -> 7fa4e3b (pr/novp.12 -> pr/novp.13, compare) after #19099 merge and conflicts due to bitcoin-core/gui#35 and #19099
Rebased 7fa4e3b -> 9070db9 (pr/novp.13 -> pr/novp.14, compare) due to conflicts with #19717 and #19671
Rebased 9070db9 -> 3df8f44 (pr/novp.14 -> pr/novp.15, compare) due to conflict with #19754

ryanofsky added a commit to ryanofsky/bitcoin that referenced this pull request May 29, 2020
Qt tests currently are currently using two NodeContext structs at the same
time, one in interfaces::NodeImpl::m_context, and the other in
BasicTestingSetup::m_node, and the tests have hacks transferring state between
them.

Fix this by getting rid of the NodeImpl::m_context struct and making it a
pointer. This way a common BitcoinApplication object can be used for all qt
tests, but they can still have their own testing setups.

Non-test code is changing but non-test behavior is still the same as before.

Benefit of this PR is to being able to remove the existing
"std::move(test.m_node.connman)" and mempool hacks.

Motivation for this PR is to let qt wallet tests continue working when the
::vpwallets global variable is removed bitcoin#19101 and not have to add even more
complicated hacks exposing duplicate WalletContext and WalletClient instances
in addition to the duplicate NodeContext instances and having to manipulate
them in the test setup.
@promag
Copy link
Contributor

promag commented May 31, 2020

Concept ACK, need to update commits in OP.

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.

Tested ACK 4be544c.


#ifdef ENABLE_WALLET
// Delete wallet controller here manually, instead of relying on Qt
// reference counting. This makes sure walletmodel m_handle_* notification
Copy link
Contributor

Choose a reason for hiding this comment

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

fe45c37

instead of relying on Qt reference counting.

What Qt reference counting?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

re: #19101 (comment)

fe45c37

instead of relying on Qt reference counting.

What Qt reference counting?

Thanks, changed "reference counting" to "object tracking" and linked to https://doc.qt.io/qt-5/objecttrees.html

// wallet implementations. It also avoids these notifications having to be
// handled while GUI objects are being destroyed, making GUI code less
// fragile as well.
delete m_wallet_controller;
Copy link
Contributor

Choose a reason for hiding this comment

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

fe45c37

Just noting that its not necessary to check for WalletModel::isWalletEnabled()), delete nullptr is safe.

struct WalletContext {
interfaces::Chain* chain{nullptr};
ArgsManager* args{nullptr};
RecursiveMutex wallets_mutex;
Copy link
Contributor

Choose a reason for hiding this comment

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

4be544c

It seems this can be a Mutex, just a suggestion for follow-up.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

re: #19101 (comment)

4be544c

It seems this can be a Mutex, just a suggestion for follow-up.

Thanks went ahead and made the change here. It's not an immediately obvious change because the mutex is held while calling wallet_load_fns callbacks, but looking at the callbacks registered in GUI code it seems like these will never change the list of wallets so it seems good change to get rid of the recursive mutex now to prevent more complexity in the future

Copy link
Contributor

@promag promag Jun 7, 2021

Choose a reason for hiding this comment

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

I also tested Mutex and built it with TSAN, LGTM

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 review! Made some updates based on comments.


Updated 4be544c -> eb389b3 (pr/novp.24 -> pr/novp.25, compare) with review tweaks


#ifdef ENABLE_WALLET
// Delete wallet controller here manually, instead of relying on Qt
// reference counting. This makes sure walletmodel m_handle_* notification
Copy link
Contributor Author

Choose a reason for hiding this comment

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

re: #19101 (comment)

fe45c37

instead of relying on Qt reference counting.

What Qt reference counting?

Thanks, changed "reference counting" to "object tracking" and linked to https://doc.qt.io/qt-5/objecttrees.html

struct WalletContext {
interfaces::Chain* chain{nullptr};
ArgsManager* args{nullptr};
RecursiveMutex wallets_mutex;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

re: #19101 (comment)

4be544c

It seems this can be a Mutex, just a suggestion for follow-up.

Thanks went ahead and made the change here. It's not an immediately obvious change because the mutex is held while calling wallet_load_fns callbacks, but looking at the callbacks registered in GUI code it seems like these will never change the list of wallets so it seems good change to get rid of the recursive mutex now to prevent more complexity in the future

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.

Tested ACK eb389b3.

@kiminuo
Copy link
Contributor

kiminuo commented Jun 9, 2021

@ryanofsky I find your PR good. It's easier to follow for me than the code in master branch. Concept ACK.

I can see that #15638 of yours added load.h and load.cpp with functions like VerifyWallets(..), LoadWallets(..), etc. Now this PR adds WalletContext struct and WalletClient now contains WalletContext* context() function.

I had a look where the functions from load.h are used and it looks like they are used only in WalletClientImpl (please correct me if I'm wrong). This brings me to my original question I had when I first glanced at your PR: Is it necessary to have the context struct? I mean what would happen if the functions from load.h were moved to WalletClientImpl and a test like the following one (https://github.com/bitcoin/bitcoin/pull/19101/files#diff-0bd88ab96ab5927299eb4e0d92d63ecbf9c8ee5753b918edc4c0eef38ce5d2c7) was replaced from:

WalletContext& context = *node.walletClient().context();
AddWallet(context, wallet);
WalletModel walletModel(interfaces::MakeWallet(context, wallet), clientModel, platformStyle.get());
RemoveWallet(context, wallet, std::nullopt);

to:

WalletClient& walletClient = *node.walletClient();
walletClient.AddWallet(wallet);
// WalletModel walletModel(interfaces::MakeWallet(context, wallet), clientModel, platformStyle.get()); // not sure here
walletClient.RemoveWallet(wallet, std::nullopt);

I don't really know if it is possible at all, I would just love to hear your opinion on this.

@ryanofsky
Copy link
Contributor Author

You could merge WalletContext and interfaces::WalletClientImpl and move code from load.cpp to interfaces.cpp. If anything though, I'd like to do the opposite and make interfaces.cpp contain as little functionality as possible so interfaces::Wallet just serves the limited purpose of exposing a well-defined interface to the wallet from external GUI code, and interfaces::WalletClient is only used to expose an interface to the wallet from external node code. I think ideally internal wallet would remain more modular (database code separate from key management code separate from syncing code separate from balance tracking code), and not reference these interfaces which tie everything together.

I think I'm not understanding the advantages of the specific Qt apptest change you mentioned. Qt test code does access internals of many layers because some checks are easier to do at the GUI layer, other are easier to do at the wallet layer, etc. I think it's important for this to be possible, but in general the interfaces should be designed more to hide internals than expose them.

As for wallet load code it's true it is only called in one place. Probably it is missing some test coverage. I would like to move more wallet loading code from wallet.cpp to load.cpp and I think improve way it handles race conditions and handles errors as discussed in #19300. But whatever improvements can be made to wallet/load.cpp I think it does make sense for it to exist as a separate file.

@kiminuo
Copy link
Contributor

kiminuo commented Jun 9, 2021

I think ideally internal wallet would remain more modular (database code separate from key management code separate from syncing code separate from balance tracking code), and not reference these interfaces which tie everything together.

I certainly do not suggest to make code worse from modularity point of view. I wrote my comment to understand better how/why you implemented this PR as you did. Thank you for the explanation. Also I did not write the comment to suggest any modifications to this PR but to understand what would happen if instead of function(context, parameters) (e.g. AddWallet(context, wallet);) one would have object.function(parameters) with no context whatsoever. And again it's just a question. Your approach is certainly straightforward.

I spent several hours trying to get familiar with various wallet classes and it still feels like I only scratched the surface, so that's why your insights here may be very valuable to other reviewers as well.

@ryanofsky
Copy link
Contributor Author

Thanks for asking these questions! I'm very happy to answer them, and I hope I didn't give the impression that I was disagreeing with anything. I was trying to answer "Is it necessary to have the context struct?" and say I don't think it's necessary, but I do think it's good because I think it's more lightweight than WalletClient and shouldn't get in the way of modularity.

Comment on lines +167 to +170
WalletContext& context = *node.walletClient().context();
AddWallet(context, wallet);
WalletModel walletModel(interfaces::MakeWallet(context, wallet), clientModel, platformStyle.get());
RemoveWallet(context, wallet, std::nullopt);
Copy link
Contributor

Choose a reason for hiding this comment

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

(This snippet is exactly the same as in src/qt/test/addressbooktests.cpp but I must say I rather double checked with my editor's text search.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

re: #19101 (comment)

(This snippet is exactly the same as in src/qt/test/addressbooktests.cpp but I must say I rather double checked with my editor's text search.)

Duplication is good to note, but it precedes this PR, and I think trying to simplify or dedup here would be more invasive and make more sense as a separate change. (This PR is just adding function parameters. Trying to replace the actual function calls would be a bigger change.)

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, that was just an observation. I agree that it is better to address that in another PR if at all.

return HandleLoadWallet(std::move(fn));
return HandleLoadWallet(m_context, std::move(fn));
}
WalletContext* context() override { return &m_context; }
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it be possible to pass WalletContext via constructor to WalletClientImpl? It seems that in that case, this function may not be necessary and a test can still create WalletClientImpl.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

re: #19101 (comment)

Would it be possible to pass WalletContext via constructor to WalletClientImpl? It seems that in that case, this function may not be necessary and a test can still create WalletClientImpl.

I think that could work but it seems a little better to me if instead of passing around WalletClient / WalletContext pairs, that if tests are already using WalletClient interfaces, they prefer using them, and only access the internal context as a fallback so they are less tied to implementation details. I think having a context method does a good job of encouraging using the external interface while making internal access still possible.

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 review! Responded and implemented some of the suggestions


Rebased eb389b3 -> 6ef719f (pr/novp.25 -> pr/novp.26, compare) on top of bitcoin-core/gui#360, with review suggestions
Rebased 6ef719f -> df609b1 (pr/novp.26 -> pr/novp.27, compare) due to conflict with #21866

return HandleLoadWallet(std::move(fn));
return HandleLoadWallet(m_context, std::move(fn));
}
WalletContext* context() override { return &m_context; }
Copy link
Contributor Author

Choose a reason for hiding this comment

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

re: #19101 (comment)

Would it be possible to pass WalletContext via constructor to WalletClientImpl? It seems that in that case, this function may not be necessary and a test can still create WalletClientImpl.

I think that could work but it seems a little better to me if instead of passing around WalletClient / WalletContext pairs, that if tests are already using WalletClient interfaces, they prefer using them, and only access the internal context as a fallback so they are less tied to implementation details. I think having a context method does a good job of encouraging using the external interface while making internal access still possible.

Comment on lines +167 to +170
WalletContext& context = *node.walletClient().context();
AddWallet(context, wallet);
WalletModel walletModel(interfaces::MakeWallet(context, wallet), clientModel, platformStyle.get());
RemoveWallet(context, wallet, std::nullopt);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

re: #19101 (comment)

(This snippet is exactly the same as in src/qt/test/addressbooktests.cpp but I must say I rather double checked with my editor's text search.)

Duplication is good to note, but it precedes this PR, and I think trying to simplify or dedup here would be more invasive and make more sense as a separate change. (This PR is just adding function parameters. Trying to replace the actual function calls would be a bigger change.)

@ryanofsky
Copy link
Contributor Author

Base PR bitcoin-core/gui#360 is now merged so this PR doesn't have any dependencies


Rebased df609b1 -> 6e6a7e1 (pr/novp.27 -> pr/novp.28, compare) after base PR bitcoin-core/gui#360 merged, also fixing conflict with #22359

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 6e6a7e1

Thanks Russ for your continued great work!

Move global wallet variables to WalletContext struct
@ryanofsky
Copy link
Contributor Author

Rebased 6e6a7e1 -> 62a09a3 (pr/novp.28 -> pr/novp.29, compare) due to conflict with #22541

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 62a09a3

@achow101
Copy link
Member

ACK 62a09a3

@fanquake
Copy link
Member

@kiminuo maybe you'll want to follow up here given some of the comments up thread?

@kiminuo
Copy link
Contributor

kiminuo commented Aug 19, 2021

@kiminuo maybe you'll want to follow up here given some of the comments up thread?

Yes, will do. Thanks for the notification.

// Schedule periodic wallet flushes and tx rebroadcasts
if (args.GetBoolArg("-flushwallet", DEFAULT_FLUSHWALLET)) {
scheduler.scheduleEvery(MaybeCompactWalletDB, std::chrono::milliseconds{500});
if (context.args->GetBoolArg("-flushwallet", DEFAULT_FLUSHWALLET)) {
Copy link
Member

Choose a reason for hiding this comment

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

Previously this was an Assert, now it is UB. Obviously doesn't matter, but I wanted to point out the difference.

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.

8 participants