-
Notifications
You must be signed in to change notification settings - Fork 37.4k
refactor: introduce generic 'Result' class and connect it to CreateTransaction and GetNewDestination #25218
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
CreateTransaction
and GetNewDestination
914e095
to
a19f1dc
Compare
Concept ACK |
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. |
a19f1dc
to
d9ffeab
Compare
rebased, conflicts solved. |
d9ffeab
to
043714c
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.
Approach ACK
This basically seems like a good design. In the simple case, you call a function and it can either succeed or fail, and if it fails it will provide an error string. In this case, the function returns In the slightly more complicated case, the function has a return value. Maybe it's an informational return value, or maybe it's a return value with details about errors if the caller needs to handle different errors in different ways. Either way, Some design questions / things I'm not sure about:
No need to resolve these questions to merge the PR, I think. Can add result classes now, and experiment with using them, and add/remove features as needed. |
Concept ACK. I like this rust-inspired idea. |
I also wonder why this needs three members and 6 constructors. Can this be implemented in less code by just using a Going to the extreme, with just two lines of code: template <typename Obj, typename Err>
using CallResult = std::variant<Obj, Err>; |
…everal code cleanups. fd5c996 wallet: GetAvailableBalance, remove double walk-through every available coin (furszy) 162d4ad wallet: add 'only_spendable' filter to AvailableCoins (furszy) cdf185c wallet: remove unused IsSpentKey(hash, index) method (furszy) 4b83bf8 wallet: avoid extra IsSpentKey -> GetWalletTx lookups (furszy) 3d8a282 wallet: decouple IsSpentKey(scriptPubKey) from IsSpentKey(hash, n) (furszy) a06fa94 wallet: IsSpent, 'COutPoint' arg instead of (hash, index) (furszy) 91902b7 wallet: IsLockedCoin, 'COutPoint' arg instead of (hash, index) (furszy) 9472ca0 wallet: AvailableCoins, don't call 'wtx.tx->vout[i]' multiple times (furszy) 4ce235e wallet: return 'CoinsResult' struct in `AvailableCoins` (furszy) Pull request description: This started in #24845< 8000 /a> but grew out of scope of it. So, points tackled: 1) Avoid extra `GetWalletTx` lookups inside `AvailableCoins -> IsSpentKey`. `IsSpentKey` was receiving the tx hash and index to internally lookup the tx inside the wallet's map. As all the `IsSpentKey` function callers already have the wtx available, them can provide the `scriptPubKey` directly. 2) Most of the time, we call `Wallet::AvailableCoins`, and later on the process, skip the non-spendable coins from the result in subsequent for-loops. So to speedup the process: introduced the ability to filter by "only_spendable" coins inside `Wallet::AvailableCoins` directly. (the non-spendable coins skip examples are inside `AttemptSelection->GroupOutputs` and `GetAvailableBalance`). 4) Refactored `AvailableCoins` in several ways: a) Now it will return a new struct `CoinsResult` instead of receiving the vCoins vector reference (which was being cleared at the beginning of the method anyway). --> this is coming from #24845 but cherry-picked it here too to make the following commits look nicer. b) Unified all the 'wtx.tx->vout[I]' calls into a single call (coming from this comment #24699 (comment)). 5) The wallet `IsLockedCoin` and `IsSpent` methods now accept an `OutPoint` instead of a hash:index. Which let me cleanup a bunch of extra code. 6) Speeded up the wallet 'GetAvailableBalance': filtering `AvailableCoins` by spendable outputs only and using the 'AvailableCoins' retrieved `total_amount` instead of looping over all the retrieved coins once more. ------------------------------------------------------- Side topic, all this process will look even nicer with #25218 ACKs for top commit: achow101: ACK fd5c996 brunoerg: crACK fd5c996 w0xlt: Code Review ACK fd5c996 Tree-SHA512: 376a85476f907f4f7d1fc3de74b3dbe159b8cc24687374d8739711ad202ea07a33e86f4e66dece836da3ae6985147119fe584f6e672f11d0450ba6bd165b3220
Thanks @w0xlt for the review!
Good eye. Seems that it's a silent conflict with master due #24924 merge. Going to rebase it once more. |
26288ac
to
2ad9f01
Compare
All green now, ready to go. |
Useful to encapsulate the function result object (in case of having it) or, in case of failure, the failure reason. This let us clean lot of boilerplate code, as now instead of returning a boolean and having to add a ref arg for the return object and another ref for the error string. We can simply return a 'BResult<Obj>'. Example of what we currently have: ``` bool doSomething(arg1, arg2, arg3, arg4, &result, &error_string) { do something... if (error) { error_string = "something bad happened"; return false; } result = goodResult; return true; } ``` Example of what we will get with this commit: ``` BResult<Obj> doSomething(arg1, arg2, arg3, arg4) { do something... if (error) return {"something happened"}; // good return {goodResult}; } ``` This allows a similar boilerplate cleanup on the function callers side as well. They don't have to add the extra pre-function-call error string and result object declarations to pass the references to the function.
2ad9f01
to
111ea3a
Compare
Rebased once more to include the recently merged #25337 here as well. |
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.
reACK 111ea3a
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-ACK 111ea3a
ACK 111ea3a |
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 ACK 111ea3a 🎏
Show signature
Signature:
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512
review ACK 111ea3ab711414236f8678566a7884d48619b2d8 🎏
-----BEGIN PGP SIGNATURE-----
iQGzBAEBCgAdFiEE+rVPoUahrI9sLGYTzit1aX5ppUgFAlwqrYAACgkQzit1aX5p
pUjrYAv9ELJ7MVWMc6ec+xuvKeVMTZllPyJE0En9XpOhlJO5/icp4mlsxbVZeAN9
9Hr5KE5gDE1YFWsBfIj/SAFVLURDx0vwZkCwcxqD8IsZL7PzZN8wUrnvJVgnL8FD
ul16AsIY7iy7JJabgdfBKOcSP8TMzma3xmKRZdOHtXwy33krjvVRT9fIj+04Lb3R
R3ODVlNZg4SQY3oZPBG06XhkBoSroU4mrYnE6BEYWG1iEYlJQ1n3Ivx9qNwweGWt
EDtHF9idu/pZJXS8aVzXZLjaLq/ALoLzvy9mtHVUv+OqOQ/Cvj/jfzhfm5CancE7
idf0Ku0bEO7NvxBQ90MUudnVZb3DSOcWpPZOGOv2lYdFq96eYF0TXPe8527NsM6O
CGikleM5JmI0Do/RAzvASiJmdnktFspCVXnl8Pto1I62LdMJDwnRKVU4/itvMyAn
swH8m6dRuX/Kn8bbzSdHKvCxx5fACxAyp2/sa/BD1IEkLq1VUFWrkbBu7PSw7QC4
K4XqkLYm
=SbFr
-----END PGP SIGNATURE-----
bool HasRes() const { return std::holds_alternative<T>(m_variant); } | ||
|
||
/* In case of success, the result object */ | ||
const T& GetObj() const { |
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.
might be good to add a mutable getter, so that the result can be de-capsulated by the caller
Edit: Fixed in #25594
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.
Also LIFETIMEBOUND?
} | ||
|
||
/* In case of failure, the error cause */ | ||
const bilingual_str& GetError() const { |
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.
Shouldn't this be annotated with LIFETIMEBOUND as it is returning a pointer to internal memory?
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.
Diff to reproduce use-after-free with asan:
diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp
index fda56ccff7..2abf2db298 100644
--- a/src/test/util_tests.cpp
+++ b/src/test/util_tests.cpp
@@ -4,6 +4,7 @@
#include <util/system.h>
+#include <util/result.h>
#include <clientversion.h>
#include <fs.h>
#include <hash.h> // For Hash()
@@ -2659,6 +2660,13 @@ BOOST_AUTO_TEST_CASE(message_hash)
BOOST_CHECK_NE(message_hash1, signature_hash);
}
+BOOST_AUTO_TEST_CASE(bresult)
+{
+ const auto& long_str{"Looooooooooooooooooooooooooooooooooooooooooooooooong string"};
+ const auto& err{BResult<int>{Untranslated(long_str)}.GetError()};
+ BOOST_CHECK_EQUAL(err.original, long_str);
+}
+
BOOST_AUTO_TEST_CASE(remove_prefix)
{
BOOST_CHECK_EQUAL(RemovePrefix("./util/system.h", "./"), "util/system.h");
With the warning added:
diff --git a/src/util/result.h b/src/util/result.h
index dcf5edaa5b..18364b3c6e 100644
--- a/src/util/result.h
+++ b/src/util/result.h
@@ -6,6 +6,8 @@
#define BITCOIN_UTIL_RESULT_H
#include <util/translation.h>
+#include <attributes.h>
+
#include <variant>
/*
@@ -32,7 +34,7 @@ public:
}
/* In case of failure, the error cause */
- const bilingual_str& GetError() const {
+ const bilingual_str& GetError() const LIFETIMEBOUND {
assert(!HasRes());
return std::get<bilingual_str>(m_variant);
}
Results in:
test/util_tests.cpp:2666:19: warning: temporary bound to local reference 'err' will be destroyed at the end of the full-expression [-Wdangling]
const auto& err{BResult<int>{Untranslated(long_str)}.GetError()};
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 warning generated.
@@ -964,7 +962,7 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal( | |||
feeCalc.est.fail.start, feeCalc.est.fail.end, | |||
(feeCalc.est.fail.totalConfirmed + feeCalc.est.fail.inMempool + feeCalc.est.fail.leftMempool) > 0.0 ? 100 * feeCalc.est.fail.withinTarget / (feeCalc.est.fail.totalConfirmed + feeCalc.est.fail.inMempool + feeCalc.est.fail.leftMempool) : 0.0, | |||
feeCalc.est.fail.withinTarget, feeCalc.est.fail.totalConfirmed, feeCalc.est.fail.inMempool, feeCalc.est.fail.leftMempool); | |||
return CreatedTransactionResult(tx, nFeeRet, nChangePosInOut); | |||
return CreatedTransactionResult(tx, nFeeRet, nChangePosInOut, feeCalc); |
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.
Could use {}
to clarify that this is not a function and to enable narrowing warnings for the int types.
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.
@MarcoFalke Use {}
where? You mean CreatedTransactionResult{tx, nFeeRet, nChangePosInOut, feeCalc}
instead of current version?
|
||
auto& newTx = transaction.getWtx(); | ||
newTx = m_wallet->createTransaction(vecSend, coinControl, !wallet().privateKeysDisabled() /* sign */, nChangePosRet, nFeeRequired, error); | ||
const auto& res = m_wallet->createTransaction(vecSend, coinControl, !wallet().privateKeysDisabled() /* sign */, nChangePosRet, nFeeRequired); |
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.
while touching this line: clang-tidy named args are of the format /*sign=*/
std::optional<CreatedTransactionResult> txr_grouped = CreateTransactionInternal(wallet, vecSend, change_pos, error2, tmp_cc, sign); | ||
auto res_tx_grouped = CreateTransactionInternal(wallet, vecSend, change_pos, tmp_cc, sign); | ||
// Helper optional class for now | ||
std::optional<CreatedTransactionResult> txr_grouped{res_tx_grouped.HasRes() ? std::make_optional(res_tx_grouped.GetObj()) : std::nullopt}; |
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 use std::move
here, if the mutable BResult interface is implemented
const auto& dest = wallet.GetNewDestination(OutputType::BECH32, ""); | ||
assert(dest.HasRes()); |
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 write shorter as const CTxDestination& dest{Assert(wallet.GetNewDestination(OutputType::BECH32, "")).GetObj()};
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.
Actually nvm. This would likely be a use-after-free.
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 there could be a ReleaseObj
helper to obtain the ownership?
* (in case of success) or propagating the error cause. | ||
*/ | ||
template<class T> | ||
class BResult { |
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.
What does that B
means at the beginning of class name?
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.
Comes from "Bilingual". The story behind it is in the last paragraph of this comment: #25218 (comment)
@@ -964,7 +962,7 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal( | |||
feeCalc.est.fail.start, feeCalc.est.fail.end, | |||
(feeCalc.est.fail.totalConfirmed + feeCalc.est.fail.inMempool + feeCalc.est.fail.leftMempool) > 0.0 ? 100 * feeCalc.est.fail.withinTarget / (feeCalc.est.fail.totalConfirmed + feeCalc.est.fail.inMempool + feeCalc.est.fail.leftMempool) : 0.0, | |||
feeCalc.est.fail.withinTarget, feeCalc.est.fail.totalConfirmed, feeCalc.est.fail.inMempool, feeCalc.est.fail.leftMempool); | |||
return CreatedTransactionResult(tx, nFeeRet, nChangePosInOut); | |||
return CreatedTransactionResult(tx, nFeeRet, nChangePosInOut, feeCalc); |
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.
@MarcoFalke Use {}
where? You mean CreatedTransactionResult{tx, nFeeRet, nChangePosInOut, feeCalc}
instead of current version?
I think this PR can have |
There is a follow-up in case anyone wants to review it: #25721 |
…om `GetReservedDestination` methods 76b3c37 refactor: wallet: return util::Result from `GetReservedDestination` methods (Sebastian Falbesoner) Pull request description: This PR is a follow-up to #25218, as suggested in comment bitcoin/bitcoin#25218 (comment). The interfaces of the methods `ReserveDestination::GetReservedDestination`, `{Legacy,Descriptor,}ScriptPubKeyMan::GetReservedDestination` are improved by returning `util::Result<CTxDestination>` instead of `bool` in order to get rid of the two `CTxDestination&` and `bilingual_str&` out-parameters. ACKs for top commit: furszy: ACK 76b3c37 Tree-SHA512: bf15560a88d645bcf8768024013d36012cd65caaa4a613e8a055dfd8f29cb4a219c19084606992bad177920cdca3a732ec168e9b9526f9295491f2cf79cc6815
Summary: > Useful to encapsulate the function result object (in case of having it) or, in case of failure, the failure reason. > > This let us clean lot of boilerplate code, as now instead of returning a boolean and having to add a ref arg for the > return object and another ref for the error string. We can simply return a 'BResult<Obj>'. > > Example of what we currently have: > ``` > bool doSomething(arg1, arg2, arg3, arg4, &result, &error_string) { > do something... > if (error) { > error_string = "something bad happened"; > return false; > } > > result = goodResult; > return true; > } > ``` > > Example of what we will get with this commit: > ``` > BResult<Obj> doSomething(arg1, arg2, arg3, arg4) { > do something... > if (error) return {"something happened"}; > > // good > return {goodResult}; > } > ``` > > This allows a similar boilerplate cleanup on the function callers side as well. They don't have to add the extra > pre-function-call error string and result object declarations to pass the references to the function. bitcoin/bitcoin@7a45c33 ---- > Prepare BResult for non-copyable types bitcoin/bitcoin@fa8de09 ---- > Refactor: Replace BResult with util::Result > > Rename `BResult` class to `util::Result` and update the class interface to be > more compatible with `std::optional` and with a full-featured result class > implemented in bitcoin/bitcoin#25665. Motivation for > this change is to update existing `BResult` usages now so they don't have to > change later when more features are added in #25665. > > This change makes the following improvements originally implemented in #25665: > > - More explicit API. Drops potentially misleading `BResult` constructor that > treats any bilingual string argument as an error. Adds `util::Error` > constructor so it is never ambiguous when a result is being assigned an error > or non-error value. > > - Better type compatibility. Supports `util::Result<bilingual_str>` return > values to hold translated messages which are not errors. > > - More standard and consistent API. `util::Result` supports most of the same > operators and methods as `std::optional`. `BResult` had a less familiar > interface with `HasRes`/`GetObj`/`ReleaseObj` methods. The Result/Res/Obj > naming was also not internally consistent. > > - Better code organization. Puts `src/util/` code in the `util::` namespace so > naming reflects code organization and it is obvious where the class is coming > from. Drops "B" from name because it is undocumented what it stands for > (bilingual?) > > - Has unit tests. bitcoin/bitcoin@a23cca5 ---- > util: Add void support to util::Result > > A minimal (but hacky) way to add support for void to Result > originally posted bitcoin/bitcoin#27632 (comment) bitcoin/bitcoin@5f49cb1 ---- This is a partial backport of [[bitcoin/bitcoin#25218 | core#25218]], [[bitcoin/bitcoin#25594 | core#25594]], [[bitcoin/bitcoin#25721 | core#25721]] and [[bitcoin/bitcoin#25977 | core#25977]] The wallet use cases in the first 3 pull requests are not yet applicable to Bitcoin ABC due to missing dependencies. The `std::optional<bilingual_str>` use cases from [[bitcoin/bitcoin#25977 | core#25977]] will be backported in their own diff. Test Plan: `ninja && ninja check` Reviewers: #bitcoin_abc, Fabien Reviewed By: #bitcoin_abc, Fabien Subscribers: Fabien Differential Revision: https://reviews.bitcoinabc.org/D14991
Based on a common function signature pattern that we have all around the sources:
Introduced a generic class
BResult
that encapsulate the function boolean result, the result object (in case of having it) and, in case of failure, the string error reason.Obtaining in this way cleaner function signatures and removing boilerplate code:
Same cleanup applies equally to the function callers' side as well. There is no longer need to add the error string and the result object declarations before calling the function:
Before:
Now:
Initial Implementation:
Have connected this new concept to two different flows for now:
CreateTransaction
flow. --> 7ba2b87GetNewDestination
flow. --> bcee091Happy note: even when introduced a new class into the sources, the amount of lines removed is almost equal to added ones :).
Extra note: this work is an extended version (and a decoupling) of the work that is inside #24845 (which does not contain the
GetNewDestination
changes nor the inclusion of theFeeCalculation
field insideCreatedTransactionResult
).