-
Notifications
You must be signed in to change notification settings - Fork 37.7k
refactor: Return util::Result
from WalletLoader methods
#25616
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
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
left a quick review
src/qt/walletcontroller.cpp
Outdated
auto wallet{node().walletLoader().loadWallet(path, m_warning_message)}; | ||
|
||
if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(wallet)); | ||
m_error_message = wallet ? bilingual_str{} : wallet.GetError(); | ||
|
||
if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(wallet.ReleaseObj()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
auto wallet{node().walletLoader().loadWallet(path, m_warning_message)}; | |
if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(wallet)); | |
m_error_message = wallet ? bilingual_str{} : wallet.GetError(); | |
if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(wallet.ReleaseObj()); | |
auto wallet{node().walletLoader().loadWallet(path, m_warning_message)}; | |
if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(wallet.ReleaseObj()); | |
else m_error_message = wallet.GetError(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in 948651d.
src/qt/walletcontroller.cpp
Outdated
auto wallet{node().walletLoader().createWallet(name, m_passphrase, flags, m_warning_message)}; | ||
|
||
if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(wallet)); | ||
m_error_message = wallet ? bilingual_str{} : wallet.GetError(); | ||
|
||
if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(wallet.ReleaseObj()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
auto wallet{node().walletLoader().createWallet(name, m_passphrase, flags, m_warning_message)}; | |
if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(wallet)); | |
m_error_message = wallet ? bilingual_str{} : wallet.GetError(); | |
if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(wallet.ReleaseObj()); | |
auto res_wallet{node().walletLoader().createWallet(name, m_passphrase, flags, m_warning_message)}; | |
if (res_wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(res_wallet.ReleaseObj()); | |
else m_error_message = res_wallet.GetError(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in 3c96b25.
5eb9f50
to
324d3bb
Compare
src/qt/walletcontroller.cpp
Outdated
|
||
if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(wallet)); | ||
if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(wallet.ReleaseObj()); | ||
else m_error_message = wallet.GetError(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Below I used m_error_message = wallet ? bilingual_str{} : wallet.GetError();
It would be good to use the same code consistently everywhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in 7cbfa13. Thanks for the review.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the code change was not correctly pushed.
The commit 7cbfa13 still displays the line:
else m_error_message = wallet.GetError();
and not:
m_error_message = wallet ? bilingual_str{} : wallet.GetError();
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@shaavan I said the suggestion was addressed because I understand it's about keeping all code consistent across all commits.
I had originally defined all m_error_message
same way as done in #25594: m_error_message = wallet ? bilingual_str{} : wallet.GetError();
.
So #25616 (comment) and #25616 (comment) suggested changing them to else m_error_message = res_wallet.GetError();
but I had forgotten to change it to restoreWallet()
. 7cbfa13 fixed this.
If I understand the BResult
interface correctly, it seems to be the clearest option, as BResult<T>::GetError()
will return the default bilingual_str{} :
value if T
is not set.
However, both approaches work. If the reviewers have a strong opinion about one of them, I can change it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BResult
is essentially an std::variant wrapper. It stores one object OR the other (succeed or failure). Not both.
So, it is redundant to ask m_error_message = wallet ? bilingual_str{} : wallet.GetError();
first and then ask if succeeded in another if block.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
m_error_message = wallet ? bilingual_str{} : wallet.GetError();
was intentional to reset the member that stores the error, instead of leaving the previous value, if no error occurred. However, I am fine changing it to something else.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I double checked it before comment. The error message is only set here after calling the backend method. So, the string will always be empty if no error occurs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to load a wallet with an error and then load a wallet without error on the same walletcontroller?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I missed the comment, sorry Marko.
Is it possible to load a wallet with an error and then load a wallet without error on the same walletcontroller?
You mean, restoring or opening a wallet?
Because if that is the case, there shouldn't be any problem. Every time that we restore or open a wallet we create a new WalletControllerActivity
subclass which encapsulates the error and warning messages (it's a single shot class. It gets deleted as soon as it finishes processing the action).
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. ConflictsReviewers, 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. |
324d3bb
to
7cbfa13
Compare
#25616 (comment) addressed in 7cbfa13. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK 7cbfa13.
I think this is a good use-case of BResult.
nit: Shouldn't the commits be merged into one as the changes are small and related?
src/wallet/interfaces.cpp
Outdated
@@ -549,8 +549,15 @@ class WalletLoaderImpl : public WalletLoader | |||
void stop() override { return StopWallets(m_context); } | |||
void setMockTime(int64_t time) override { return SetMockTime(time); } | |||
|
|||
//! Return a wallet interface object or an error wrapped in a BResult instance | |||
BResult<std::unique_ptr<Wallet>> makeWallet(const std::shared_ptr<CWallet>& _wallet, bilingual_str& error) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can makeWallet
be renamed to makeWalletResult
or something else? Having both createWallet
and makeWallet
is a bit confusing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in 087959d. Thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Concept ACK
- I agree with the idea of using BResult as the return type for walletloader method, as it ensures that the return value has either the result value (if succeed) or the error value (if failure) and never both.
- The code changes look clean and concise. I want to look further into this comment before ACKing the correctness of code change.
Is it possible to load a wallet with an error and then load a wallet without an error on the same walletcontroller?
- In the meantime, I would suggest squashing the commits together.
7cbfa13
to
087959d
Compare
#25616 (review) and #25616 (review) were addressed in 087959d. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK 087959d
Changes since my last review:
- Renamed
makeWallet
->makeWalletResult
. - Squashed commits.
As per @furszy explanation in this comment, since the WalletControllerActivity
class gets deleted after the loading of a wallet finishes, it is not possible to have an error message stored from a previous erroneous loading of a wallet.
Hence it is not necessary to manually reset bilingual_str{}
.
Thanks, @furszy, for the explanation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Approach ACK
src/qt/walletcontroller.cpp
Outdated
|
||
if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(wallet)); | ||
if (res_wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(res_wallet.ReleaseObj()); | ||
else m_error_message = res_wallet.GetError(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- if (res_wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(res_wallet.ReleaseObj());
- else m_error_message = res_wallet.GetError();
+ if (res_wallet) {
+ m_wallet_model = m_wallet_controller->getOrCreateWallet(res_wallet.ReleaseObj());
+ } else {
+ m_error_message = res_wallet.GetError();
+ }
Any reason why you've changed the variable name from wallet
to res_wallet
here? It's wallet
in the identical code sections below.
src/qt/walletcontroller.cpp
Outdated
|
||
if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(wallet)); | ||
if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(wallet.ReleaseObj()); | ||
else m_error_message = wallet.GetError(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(wallet.ReleaseObj());
- else m_error_message = wallet.GetError();
+ if (wallet) {
+ m_wallet_model = m_wallet_controller->getOrCreateWallet(wallet.ReleaseObj());
+ } else {
+ m_error_message = wallet.GetError();
+ }
src/qt/walletcontroller.cpp
Outdated
if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(wallet.ReleaseObj()); | ||
else m_error_message = wallet.GetError(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(wallet.ReleaseObj());
- else m_error_message = wallet.GetError();
+ if (wallet) {
+ m_wallet_model = m_wallet_controller->getOrCreateWallet(wallet.ReleaseObj());
+ } else {
+ m_error_message = wallet.GetError();
+ }
017b473
to
f179998
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review/debug build/unit tests ACK f179998
@@ -88,7 +88,7 @@ class Wallet | |||
virtual std::string getWalletName() = 0; | |||
|
|||
// Get a new address. | |||
virtual util::Result<CTxDestination> getNewDestination(const OutputType type, const std::string label) = 0; | |||
virtual util::Result<CTxDestination> getNewDestination(const OutputType type, const std::string& label) = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for picking up #25721 (comment) here and fixing up indentation in this file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The wallet method still creates a copy?
$ git grep GetNewDestination -- '*wallet.h'
src/wallet/wallet.h: util::Result<CTxDestination> GetNewDestination(const OutputType type, const std::string label);
src/wallet/interfaces.cpp
Outdated
{ | ||
DatabaseOptions options; | ||
DatabaseStatus status; | ||
ReadDatabaseArgs(*m_context.args, options); | ||
options.require_existing = true; | ||
return MakeWallet(m_context, LoadWallet(m_context, name, true /* load_on_start */, options, status, error, warnings)); | ||
bilingual_str error; | ||
util::Result<std::unique_ptr<Wallet>> wallet{MakeWallet(m_context, LoadWallet(m_context, name, true /* load_on_start */, options, status, error, warnings))}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit if you repush, here and in createWallet()
above, while touching this line can use Clang-tidy named arg format (/*load_on_start=*/
true) like in restoreWallet()
below.
src/wallet/interfaces.cpp
Outdated
if (!wallet) { | ||
return util::Error{error}; | ||
} | ||
return wallet; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit, could save 9 lines by replacing the above 4 lines in each of the 3 methods with identical logic in one line: return wallet ? wallet : util::Error{error};
(feel free to ignore)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would be good, but applying this change will result in the error below.
I'm not sure about the reason for the error.
wallet/interfaces.cpp:577:25: error: call to implicitly-deleted copy constructor of 'util::Result<std::unique_ptr<Wallet>>'
return wallet ? wallet : util::Error{error};
^~~~~~
./util/result.h:38:36: note: copy constructor of 'Result<std::unique_ptr<interfaces::Wallet>>' is implicitly deleted because field 'm_variant' has a deleted copy constructor
std::variant<bilingual_str, T> m_variant;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, std::unique_ptr
is move constructible and move assignable but not copy constructible or copy assignable. So this would work: return wallet ? std::move(wallet) : util::Error{error};
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. Done in 466374f.
So return wallet
moves the object and return wallet ? wallet : util::Error{error};
copies the object. Interesting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be that return with a ternary doesn't benefit from RVO (return value optimization) by the compiler in the same way as with an if statement.
https://en.cppreference.com/w/cpp/language/copy_elision
Edit: benefitting from RVO could (possibly, unsure) be an argument for using the more verbose if statement.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, not a blocker but this is a topic I plan to go deeper into in order to improve my understanding.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Concept ACK
nit: Same as done in the commit message, the PR title and description should also be adapted to the latest rebase (s/BResult
/util::Result
/).
BResult
from WalletLoader methodsutil::Result
from WalletLoader methods
f179998
to
466374f
Compare
CI error seems unrelated. |
.vscode/settings.json
Outdated
@@ -0,0 +1,81 @@ | |||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this file invited itself into the last push, ACK 466374f otherwise :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed. Thanks.
466374f
to
be13477
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK be13477
|
||
CMutableTransaction mtx; | ||
mtx.vout.push_back({COIN, GetScriptForDestination(dest)}); | ||
mtx.vout.push_back({COIN, GetScriptForDestination(*op_dest)}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for picking up #25721 (comment) here.
src/wallet/scriptpubkeyman.cpp
Outdated
@@ -1770,7 +1770,7 @@ bool DescriptorScriptPubKeyMan::GetReservedDestination(const OutputType type, bo | |||
} else { | |||
error = util::ErrorString(op_dest); | |||
} | |||
return bool(op_dest); | |||
return op_dest.has_value(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for picking up the suggestion #25721 (comment) here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
utACK be13477, just a small comment.
src/qt/walletcontroller.cpp
Outdated
m_error_message = util::ErrorString(wallet); | ||
if (wallet) { | ||
m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(*wallet)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why the revert here? (and in the others as well).
the result could be an object or an error, not both.
if (wallet) m_wallet_model = something;
else m_error_message = util::ErrorString(wallet);
Thinking that might be good to add an assertion inside util::ErrorString
, callers should always check that the result is an error prior to convert it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe assert(std::holds_alternative<bilingual_str>(result.m_variant));
inside util::ErrorString
?
However this assertion cause a compile-time error error: static_assert failed due to requirement '__detail::__variant::__exactly_once<bilingual_str, bilingual_str, bilingual_str>' "T must occur exactly once in alternatives"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep, that was where I was pointing to.
assert(!result.has_value());
there should work fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I applied the suggestion above (if (wallet) m_wallet_model = something;
) in ac74799.
But regarding the assertion, I think a follow-up PR is better because it also requires changing the src/test/result_tests.cpp
which expects the result of util::ErrorString
be {}
if the object has value This would increase the scope of this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing brackets in the conditionals again (e.g. #25616 (comment))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"If an if only has a single-statement then-clause, it can appear on the same line as the if, without braces. In every other case, braces are required, and the then and else clauses must appear correctly indented on a new line."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep, aren't we having a single line statement here?
if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(*wallet));
else m_error_message = util::ErrorString(wallet);
be13477
to
ac74799
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK ac74799
ac74799
to
84b08f3
Compare
84b08f3
to
07df6cd
Compare
Rebased. |
Review ACK 07df6cd |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code-review ACK 07df6cd
@@ -320,31 +320,31 @@ class WalletLoader : public ChainClient | |||
{ | |||
public: | |||
//! Create new wallet. | |||
virtual std::unique_ptr<Wallet> createWallet(const std::string& name, const SecureString& passphrase, uint64_t wallet_creation_flags, bilingual_str& error, std::vector<bilingual_str>& warnings) = 0; | |||
virtual util::Result<std::unique_ptr<Wallet>> createWallet(const std::string& name, const SecureString& passphrase, uint64_t wallet_creation_flags, std::vector<bilingual_str>& warnings) = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would probably be best to also wrap the warnings in the result? Are you working on this @ryanofsky ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
re: #25616 (comment)
Would probably be best to also wrap the warnings in the result? Are you working on this @ryanofsky ?
It needs rebase, but yes I did this in #25722
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I meant a minimal extract without the other C++ bloat (for example Result<void>
) that isn't needed for simply passing warnings
@@ -393,8 +401,11 @@ void RestoreWalletActivity::restore(const fs::path& backup_file, const std::stri | |||
QTimer::singleShot(0, worker(), [this, backup_file, wallet_name] { | |||
auto wallet{node().walletLoader().restoreWallet(backup_file, wallet_name, m_warning_message)}; | |||
|
|||
m_error_message = util::ErrorString(wallet); | |||
if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(*wallet)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I preferred the previous code, as it is shorter and less ambiguous
…r methods 07df6cd wallet: Return `util::Result` from WalletLoader methods (w0xlt) Pull request description: This PR adds a method that implement common logic to WalletLoader methods and change them to return `BResult<std::unique_ptr<Wallet>>`. Motivation: bitcoin#25594 changed `restoreWallet` to return `BResult` but this method shares a common pattern with `createWallet` and `loadWallet`. This PR keeps the same pattern to all WalletLoader methods. ACKs for top commit: jonatack: Review ACK 07df6cd theStack: Code-review ACK 07df6cd Tree-SHA512: 2fe321134883f7cce60206888113800afef0fa168dab184e1a8692cd21a231970eb9c25c7220ea39e5d457134002d47f0974463925db76abbf8dfcd421629c63
This PR adds a method that implement common logic to WalletLoader methods and change them to return
BResult<std::unique_ptr<Wallet>>
.Motivation: #25594 changed
restoreWallet
to returnBResult
but this method shares a common pattern withcreateWallet
andloadWallet
. This PR keeps the same pattern to all WalletLoader methods.