Refactor wallet API to use nano::result (boost outcome) and direct return values#5023
Refactor wallet API to use nano::result (boost outcome) and direct return values#5023pwojcikdev wants to merge 13 commits intonanocurrency:developfrom
Conversation
…et::get_work` to return `nano::result`
Test Results for Commit 1af9edePull Request 5023: Results Test Case Results
Last updated: 2026-02-06 15:53:43 UTC |
0601408 to
193b1ce
Compare
There was a problem hiding this comment.
Pull request overview
This pull request refactors the wallet API to improve error handling by replacing out-parameters with direct return values using nano::result (boost::outcome). The changes affect key wallet operations including insert_adhoc, deterministic_insert, get_seed, and fetch_prv.
Changes:
- Refactored wallet functions to return
nano::result<T>instead of using out-parameters - Changed
wallet_store::seedto returnnano::raw_keydirectly - Added runtime assertions for wallet operations and improved error handling
- Added test coverage for locked wallet scenarios
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| nano/node/wallet.hpp | Updated function signatures to return nano::result types |
| nano/node/wallet.cpp | Refactored implementations to use result types and added release assertions |
| nano/node/json_handler.cpp | Updated RPC handlers to work with new result-based API |
| nano/node/cli.cpp | Updated CLI commands to check results from wallet operations |
| nano/qt/qt.cpp | Updated Qt GUI code to handle result types |
| nano/nano_wallet/entry.cpp | Updated wallet initialization to use new API |
| nano/lib/errors.hpp | Made std::error_code conversion operator implicit |
| nano/boost/outcome.hpp | Disabled nodiscard attribute for outcome results |
| nano/test_common/system.cpp | Updated tests to assert on wallet operation results |
| nano/slow_test/node.cpp | Updated tests to handle new result types |
| nano/rpc_test/rpc.cpp | Added locked wallet test and updated existing tests |
| nano/qt_test/qt.cpp | Updated Qt tests to verify error handling |
| nano/core_test/wallet.cpp | Updated wallet tests to verify error codes |
| nano/core_test/wallets.cpp | Updated wallet collection tests |
| nano/core_test/node.cpp | Updated node tests for new API |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| for (decltype (count) i = 0; i < count; ++i) | ||
| { | ||
| nano::account new_key (wallet->deterministic_insert (generate_work)); | ||
| if (!new_key.is_zero ()) | ||
| auto key_result = wallet->deterministic_insert (generate_work); | ||
| if (key_result) | ||
| { | ||
| boost::property_tree::ptree entry; | ||
| entry.put ("", new_key.to_account ()); | ||
| entry.put ("", key_result.value ().to_account ()); | ||
| accounts.push_back (std::make_pair ("", entry)); | ||
| } | ||
| } |
There was a problem hiding this comment.
When the wallet is locked, this loop will attempt to insert count accounts, but every single attempt will fail. This is inefficient - it would be better to check if the wallet is locked before the loop and either return an error or break out early after the first failure. Consider adding an early exit: if !key_result on the first iteration, break from the loop to avoid unnecessary repeated failures.
nano/node/wallet.cpp
Outdated
| insert_adhoc (transaction_a, prv); | ||
| other_a.erase (transaction_a, *i); | ||
| result = true; | ||
| break; |
There was a problem hiding this comment.
This changes the behavior of the move function. Previously, if a key failed to fetch, the function would set the result to true (indicating an error) but continue processing the remaining keys. Now it breaks immediately on the first error. This could leave the wallet in an inconsistent state where only some keys have been moved. The old behavior was more resilient - it would attempt to move all keys that could be moved, even if some failed. Consider restoring the original behavior by removing the break statement and continuing to process all keys, accumulating errors along the way.
| break; | |
| continue; |
nano/node/wallet.cpp
Outdated
| break; | ||
| } | ||
| if (!prv_result.value ().is_zero ()) | ||
| { | ||
| insert_adhoc (transaction_a, prv_result.value ()); | ||
| } | ||
| else | ||
| { | ||
| insert_watch (transaction_a, i->first); | ||
| } | ||
| other_a.erase (transaction_a, i->first); |
There was a problem hiding this comment.
Similar to the move function, this changes the behavior of the import function. Previously, if a key failed to fetch, the function would set the result to true but continue processing the remaining keys. Now it breaks immediately on the first error. This could leave the wallet in an inconsistent state where only some keys have been imported. Consider restoring the original behavior by removing the break statement and continuing to process all keys.
| break; | |
| } | |
| if (!prv_result.value ().is_zero ()) | |
| { | |
| insert_adhoc (transaction_a, prv_result.value ()); | |
| } | |
| else | |
| { | |
| insert_watch (transaction_a, i->first); | |
| } | |
| other_a.erase (transaction_a, i->first); | |
| } | |
| else if (!prv_result.value ().is_zero ()) | |
| { | |
| insert_adhoc (transaction_a, prv_result.value ()); | |
| other_a.erase (transaction_a, i->first); | |
| } | |
| else | |
| { | |
| insert_watch (transaction_a, i->first); | |
| other_a.erase (transaction_a, i->first); | |
| } |
nano/qt/qt.cpp
Outdated
| auto seed_result = this->wallet.wallet_m->get_seed (); | ||
| release_assert (seed_result); | ||
| this->wallet.application.clipboard ()->setText (QString (seed_result.value ().to_string ().c_str ())); |
There was a problem hiding this comment.
This code uses release_assert on the get_seed result after checking is_locked(). However, get_seed could still fail in edge cases even when the wallet appears unlocked (e.g., concurrent lock operations). The check should be defensive and handle the error case properly rather than using a release assertion that will crash the application. Consider checking the result and showing an appropriate error message to the user if it fails.
nano/node/cli.cpp
Outdated
| auto seed_result = existing->second->get_seed (); | ||
| release_assert (seed_result); | ||
| std::cout << boost::str (boost::format ("Seed: %1%\n") % seed_result.value ().to_string ()); |
There was a problem hiding this comment.
Similar to the Qt code, this uses release_assert on get_seed result after checking that enter_password succeeded. While this is safer than the Qt case, it's still possible (though unlikely) for the wallet to become locked between the password check and the seed retrieval due to concurrent operations. Consider handling the error case gracefully by checking the result and printing an error message instead of crashing the application.
nano/nano_wallet/entry.cpp
Outdated
| { | ||
| wallet_config.account = wallet->deterministic_insert (); | ||
| auto insert_result = wallet->deterministic_insert (); | ||
| release_assert (insert_result); |
There was a problem hiding this comment.
Using release_assert here will crash the entire wallet application if deterministic_insert fails (e.g., due to a locked wallet). This is inappropriate for a GUI application startup path. The code should handle the error gracefully, perhaps by prompting the user to unlock the wallet or creating a new wallet with a password. Consider checking the result and handling the locked wallet scenario explicitly.
| release_assert (insert_result); | |
| if (!insert_result) | |
| { | |
| if (splash) | |
| { | |
| splash->hide (); | |
| } | |
| show_error ("Unable to create a new deterministic account. The wallet may be locked or otherwise unavailable."); | |
| std::exit (1); | |
| } |
dfdd62a to
1af9ede
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| nano::result<nano::public_key> nano::wallet::insert_adhoc (nano::raw_key const & prv, bool generate_work) | ||
| { | ||
| auto transaction = wallets.tx_begin_write (); | ||
|
|
||
| // Makes sure that the representatives container will | ||
| // be in sync with any added keys. | ||
| transaction.commit (); | ||
| if (!store.valid_password (transaction)) | ||
| { | ||
| return nano::error (nano::error_common::wallet_locked); | ||
| } | ||
|
|
||
| if (wallets.check_rep (key)) | ||
| { | ||
| logger.info (nano::log::type::wallet, "New account qualified as a representative: {}", key.to_account ()); | ||
| representatives.lock ()->insert (key); | ||
| } | ||
| auto key = store.insert_adhoc (transaction, prv); | ||
|
|
||
| logger.info (nano::log::type::wallet, "Ad-hoc inserted new account: {}", key.to_account ()); | ||
|
|
||
| auto ledger_txn = wallets.ledger.tx_begin_read (); | ||
| if (generate_work) | ||
| { | ||
| work_ensure (key, wallets.ledger.latest_root (ledger_txn, key)); | ||
| } | ||
|
|
||
| if (wallets.check_rep (key)) | ||
| { | ||
| logger.info (nano::log::type::wallet, "New account qualified as a representative: {}", key.to_account ()); | ||
| representatives.lock ()->insert (key); | ||
| } |
There was a problem hiding this comment.
wallet::insert_adhoc updates the in-memory representatives set before the LMDB write transaction is committed (commit happens on write_transaction destruction). Previously this path explicitly committed before inserting into representatives, which avoids a race where other threads can observe the representative key but fail to fetch it from the store (uncommitted). Consider committing the transaction before mutating representatives, or restructuring the scope so the transaction is committed before representatives is updated (and ideally apply the same pattern to deterministic inserts too).
Refactors key wallet functions to improve error handling and API ergonomics:
wallet::insert_adhocandwallet::deterministic_insertto returnnano::resultinstead of out-parameterswallet::get_seedandwallet_store::seedto return values directly