Wallet: Fix error handling (copy_file failure in RestoreWallet, and in general via interfaces) #26005

pull luke-jr wants to merge 2 commits into bitcoin:master from luke-jr:fix_wallet_copyfail_nullresult changing 2 files +38 โˆ’20
  1. luke-jr commented at 9:39 pm on September 4, 2022: member

    Bug 1: copy_file can throw exceptions, but RestoreWallet is expected to return a nullptr with a populated errors parameter. This is fixed by wrapping copy_file and LoadWallet (for good measure) in a try block, and converting any exceptions to the intended return style.

    Bug 2: util::Result turns what would have been a false unique_ptr into a true nullptr result, which leads to nullptr dereferences in at least the 3 cases of wallet creation/loading/restoring. This is fixed by keeping the pointer as a plain std::unique_ptr until actually returning it (ie, after the nullptr check).

    Fixes https://github.com/bitcoin-core/gui/issues/661

  2. fanquake added the label Wallet on Sep 4, 2022
  3. w0xlt approved
  4. w0xlt commented at 11:42 pm on September 4, 2022: contributor
  5. luke-jr commented at 0:05 am on September 5, 2022: member

    Not sure if b60a2ce (“Bugfix: Wallet: Wrap RestoreWallet content in a try block …”) is needed. See #22541 (comment).

    Without it, the GUI crashes quite uncleanly (https://github.com/bitcoin-core/gui/issues/661), and an empty directory is left behind.

  6. MarcoFalke added this to the milestone 24.0 on Sep 5, 2022
  7. in src/wallet/interfaces.cpp:580 in c1cd7b4ac6 outdated
    585     }
    586     util::Result<std::unique_ptr<Wallet>> restoreWallet(const fs::path& backup_file, const std::string& wallet_name, std::vector<bilingual_str>& warnings) override
    587     {
    588         DatabaseStatus status;
    589         bilingual_str error;
    590-        util::Result<std::unique_ptr<Wallet>> wallet{MakeWallet(m_context, RestoreWallet(m_context, backup_file, wallet_name, /*load_on_start=*/true, status, error, warnings))};
    


    MarcoFalke commented at 6:37 am on September 5, 2022:
    This was likely introduced in fa475e9c7977a952617738f2ee8cf600c07d4df8, and 07df6cda1468ed45ac227ac6f0169b040e5c0bf3
  8. DrahtBot commented at 6:38 am on September 5, 2022: contributor

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

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #26022 (Add util::ResultPtr class by ryanofsky)

    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.

  9. ryanofsky referenced this in commit 4b8ee99dea on Sep 6, 2022
  10. achow101 commented at 4:58 pm on September 6, 2022: member

    The try should probably be moved up to also include the TryCreateDirectories as well? If I change the permissions on the entire wallets/ directory, it still crashes.

    Or maybe TryCreateDirectories needs to be modified to handle when it actually can’t create a directory. That would also help with the case where a wallet dir can’t be created during wallet creation.

  11. in src/wallet/wallet.cpp:402 in b60a2ce138 outdated
    400-    auto wallet = LoadWallet(context, wallet_name, load_on_start, options, status, error, warnings);
    401+    try {
    402+        fs::copy_file(backup_file, wallet_file, fs::copy_options::none);
    403 
    404+        wallet = LoadWallet(context, wallet_name, load_on_start, options, status, error, warnings);
    405+    } catch (const std::exception& e) {
    


    ryanofsky commented at 5:05 pm on September 6, 2022:

    In commit “Bugfix: Wallet: Wrap RestoreWallet content in a try block to ensure exceptions become returned errors and incomplete wallet directory is removed” (b60a2ce138f4549e7233542e21cd96e0d8e0b5f8)

    This might just be difference of opinion about defensive programming, but IMO it makes code harder to reason about and maintain if it is catching exceptions that are never expected to be thrown. I think it would be better to only catch fs::filesystem_exception not std::exception, and only catch exceptions if copy_file throws, not if LoadWallet throws. I also think an error text like “failed to copy wallet” is better than “unexpected exception.” Possible suggestion:

     0--- a/src/wallet/wallet.cpp
     1+++ b/src/wallet/wallet.cpp
     2@@ -394,17 +394,16 @@ std::shared_ptr<CWallet> RestoreWallet(WalletContext& context, const fs::path& b
     3     }
     4 
     5     auto wallet_file = wallet_path / "wallet.dat";
     6-    std::shared_ptr<CWallet> wallet;
     7-
     8+    status = DatabaseStatus::SUCCESS;
     9     try {
    10         fs::copy_file(backup_file, wallet_file, fs::copy_options::none);
    11-
    12-        wallet = LoadWallet(context, wallet_name, load_on_start, options, status, error, warnings);
    13-    } catch (const std::exception& e) {
    14-        wallet.reset();  // just in case
    15-        if (!error.empty()) error += Untranslated("\n");
    16-        error += strprintf(Untranslated("Unexpected exception: %s"), e.what());
    17+    } catch (const fs::filesystem_error& e) {
    18+        error += Untranslated(strprintf(("Failed to copy wallet: %s"), e.what()));
    19+        status = DatabaseStatus::FAILED_CREATE;
    20     }
    21+
    22+    auto wallet = status == DatabaseStatus::SUCCESS ? LoadWallet(context, wallet_name, load_on_start, options, status, error, warnings) : nullptr;
    23+
    24     if (!wallet) {
    25         fs::remove(wallet_file);
    26         fs::remove(wallet_path);
    

    luke-jr commented at 7:05 pm on September 6, 2022:
    The GUI cannot handle any exception here, so either we catch everything, or we have to modify the GUI to check for other exceptions and “crash” cleanly… IMO it’s strictly better to fail to load the wallet, than to kill the program (cleanly or not).

    achow101 commented at 9:44 pm on September 6, 2022:
    I agree with @ryanofsky. LoadWallet shouldn’t throw any exceptions, and having it in this try-catch makes it seem like it could. The places within LoadWallet which are expected to throw already have their own try-catch blocks so that it returns an error rather than throwing an exception.

    luke-jr commented at 11:09 pm on September 10, 2022:
    The whole point is to catch unexpected exceptions…

    furszy commented at 0:39 am on September 11, 2022:

    I do agree with ryanofsky and achow101 here. I don’t think that we want to add try/catch everywhere around the sources if the functions aren’t meant to throw exceptions (if they throw, then we have a bug in our sources that has to be solved).

    I think that a better design would be to create a general safe connection function for QTimer::singleShot calls. Just made a quick draft in https://github.com/bitcoin-core/gui/pull/666.


    achow101 commented at 8:01 pm on September 14, 2022:

    The whole point is to catch unexpected exceptions…

    I think then it would be better to implement some overarching exception handling in the GUI rather than handling them individually like this, and having try-catches for code that is not meant to throw an exception for its errors is somewhat confusing.


    luke-jr commented at 1:00 am on September 15, 2022:
    Anything higher level wouldn’t know to just abort loading the wallet rather than kill the entire program.

    achow101 commented at 5:45 pm on September 15, 2022:
    RPC seems to be able to handle unexpected exceptions fine?

    luke-jr commented at 9:08 pm on September 16, 2022:
    Changing how the GUI handles unexpected exceptions in general is outside the scope of this PR.
  12. ryanofsky approved
  13. ryanofsky commented at 5:17 pm on September 6, 2022: contributor
    Code review ACK c1cd7b4ac60d00dec369b3fe8f883cad07fbe833
  14. luke-jr force-pushed on Sep 6, 2022
  15. luke-jr commented at 7:12 pm on September 6, 2022: member

    The try should probably be moved up to also include the TryCreateDirectories as well? If I change the permissions on the entire wallets/ directory, it still crashes.

    Done

  16. in src/wallet/wallet.cpp:403 in b298beed38 outdated
    412-    auto wallet = LoadWallet(context, wallet_name, load_on_start, options, status, error, warnings);
    413+        fs::copy_file(backup_file, wallet_file, fs::copy_options::none);
    414 
    415+        wallet = LoadWallet(context, wallet_name, load_on_start, options, status, error, warnings);
    416+    } catch (const std::exception& e) {
    417+        wallet.reset();  // just in case
    


    furszy commented at 8:06 pm on September 6, 2022:
    would remove this as it cannot realistically happen

    luke-jr commented at 9:07 pm on September 16, 2022:
    At least not with the current C++17 standard. Replaced it with an assert.
  17. furszy approved
  18. furszy commented at 8:06 pm on September 6, 2022: member
    Code review ACK b298beed
  19. MarcoFalke added the label Waiting for author on Sep 13, 2022
  20. achow101 commented at 7:13 pm on September 15, 2022: member
    ACK b298beed38fba06d354e44778e6ecb3526feec1d
  21. Bugfix: Wallet: Wrap RestoreWallet content in a try block to ensure exceptions become returned errors and incomplete wallet directory is removed 335ff98c8a
  22. in src/wallet/interfaces.cpp:565 in b298beed38 outdated
    559@@ -560,8 +560,12 @@ class WalletLoaderImpl : public WalletLoader
    560         options.create_flags = wallet_creation_flags;
    561         options.create_passphrase = passphrase;
    562         bilingual_str error;
    563-        util::Result<std::unique_ptr<Wallet>> wallet{MakeWallet(m_context, CreateWallet(m_context, name, /*load_on_start=*/true, options, status, error, warnings))};
    564-        return wallet ? std::move(wallet) : util::Error{error};
    565+        std::unique_ptr<Wallet> wallet{MakeWallet(m_context, CreateWallet(m_context, name, /*load_on_start=*/true, options, status, error, warnings))};
    566+        if (wallet) {
    567+            return std::move(wallet);
    


    achow101 commented at 7:24 pm on September 15, 2022:
    0wallet/interfaces.cpp: In member function โ€˜virtual util::Result<std::unique_ptr<interfaces::Wallet> > wallet::{anonymous}::WalletLoaderImpl::createWallet(const std::string&, const SecureString&, uint64_t, std::vector<bilingual_str>&)โ€™:
    1wallet/interfaces.cpp:565:29: warning: redundant move in return statement [-Wredundant-move]
    2  565 |             return std::move(wallet);
    3      |                    ~~~~~~~~~^~~~~~~~
    

    Similar warning in the other places in this commit.


    luke-jr commented at 9:07 pm on September 16, 2022:
    Removed redundant move.
  23. luke-jr force-pushed on Sep 16, 2022
  24. in src/wallet/interfaces.cpp:565 in 6c5af62572 outdated
    559@@ -560,8 +560,12 @@ class WalletLoaderImpl : public WalletLoader
    560         options.create_flags = wallet_creation_flags;
    561         options.create_passphrase = passphrase;
    562         bilingual_str error;
    563-        util::Result<std::unique_ptr<Wallet>> wallet{MakeWallet(m_context, CreateWallet(m_context, name, /*load_on_start=*/true, options, status, error, warnings))};
    564-        return wallet ? std::move(wallet) : util::Error{error};
    565+        std::unique_ptr<Wallet> wallet{MakeWallet(m_context, CreateWallet(m_context, name, /*load_on_start=*/true, options, status, error, warnings))};
    566+        if (wallet) {
    567+            return wallet;
    


    achow101 commented at 11:10 pm on September 16, 2022:

    … CI doesn’t like this either.

    0wallet/interfaces.cpp:565:20: error: call to implicitly-deleted copy constructor of 'std::unique_ptr<interfaces::Wallet>'
    1            return wallet;
    2                   ^~~~~~
    3/tmp/cirrus-ci-build/ci/scratch/msan/build/include/c++/v1/memory:1553:3: note: copy constructor is implicitly deleted because 'unique_ptr<interfaces::Wallet>' has a user-declared move constructor
    4  unique_ptr(unique_ptr&& __u) _NOEXCEPT
    5  ^
    6./util/result.h:44:14: note: passing argument to parameter 'obj' here
    7    Result(T obj) : m_variant{std::in_place_index_t<1>{}, std::move(obj)} {}
    8             ^
    

    I think this will work:

    0            return {std::move(wallet)};
    

    luke-jr commented at 11:28 pm on September 16, 2022:
    ๐Ÿคจ
  25. Bugfix: Wallet: Return util::Error rather than non-error nullptr when CreateWallet/LoadWallet/RestoreWallet fail c3e536555a
  26. luke-jr force-pushed on Sep 16, 2022
  27. achow101 commented at 1:05 am on September 17, 2022: member
    ACK c3e536555aa3a7db773170671da1256a2ace2094
  28. MarcoFalke removed the label Waiting for author on Sep 17, 2022
  29. fanquake merged this on Sep 19, 2022
  30. fanquake closed this on Sep 19, 2022

  31. sidhujag referenced this in commit 3bd691fae1 on Sep 20, 2022
  32. ryanofsky referenced this in commit b69cc6f1fa on Sep 20, 2022
  33. bitcoin locked this on Sep 19, 2023

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2024-12-19 06:12 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me