refactor: Return util::Result from WalletLoader methods #25616

pull w0xlt wants to merge 1 commits into bitcoin:master from w0xlt:brrrrresult-load-create changing 4 files +47 −35
  1. w0xlt commented at 10:54 pm on July 14, 2022: contributor

    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 return BResult but this method shares a common pattern with createWallet and loadWallet. This PR keeps the same pattern to all WalletLoader methods.

  2. in src/qt/walletcontroller.cpp:349 in 5eb9f506e1 outdated
    349+        auto wallet{node().walletLoader().loadWallet(path, m_warning_message)};
    350 
    351-        if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(wallet));
    352+        m_error_message = wallet ? bilingual_str{} : wallet.GetError();
    353+
    354+        if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(wallet.ReleaseObj());
    


    furszy commented at 11:24 pm on July 14, 2022:
    0auto wallet{node().walletLoader().loadWallet(path, m_warning_message)};
    1
    2if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(wallet.ReleaseObj());
    3else m_error_message = wallet.GetError();
    

  3. in src/qt/walletcontroller.cpp:269 in 5eb9f506e1 outdated
    266+        auto wallet{node().walletLoader().createWallet(name, m_passphrase, flags, m_warning_message)};
    267 
    268-        if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(wallet));
    269+        m_error_message = wallet ? bilingual_str{} : wallet.GetError();
    270+
    271+        if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(wallet.ReleaseObj());
    


    furszy commented at 11:33 pm on July 14, 2022:
    0auto res_wallet{node().walletLoader().createWallet(name, m_passphrase, flags, m_warning_message)};
    1
    2if (res_wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(res_wallet.ReleaseObj());
    3else m_error_message = res_wallet.GetError();
    

  4. furszy commented at 11:35 pm on July 14, 2022: member
    left a quick review
  5. DrahtBot added the label Refactoring on Jul 14, 2022
  6. w0xlt force-pushed on Jul 15, 2022
  7. in src/qt/walletcontroller.cpp:350 in 324d3bb6d7 outdated
    343@@ -343,9 +344,10 @@ void OpenWalletActivity::open(const std::string& path)
    344         tr("Opening Wallet <b>%1</b>…").arg(name.toHtmlEscaped()));
    345 
    346     QTimer::singleShot(0, worker(), [this, path] {
    347-        std::unique_ptr<interfaces::Wallet> wallet = node().walletLoader().loadWallet(path, m_error_message, m_warning_message);
    348+        auto wallet{node().walletLoader().loadWallet(path, m_warning_message)};
    349 
    350-        if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(wallet));
    351+        if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(wallet.ReleaseObj());
    352+        else m_error_message = wallet.GetError();
    


    MarcoFalke commented at 8:00 am on July 15, 2022:

    Below I used m_error_message = wallet ? bilingual_str{} : wallet.GetError();

    It would be good to use the same code consistently everywhere.


    w0xlt commented at 4:56 pm on July 15, 2022:

    shaavan commented at 12:50 pm on July 16, 2022:

    I think the code change was not correctly pushed. The commit 7cbfa1335bd89d5831e916672e57aa8601b12bb4 still displays the line:

    0else m_error_message = wallet.GetError();
    

    and not:

    0m_error_message = wallet ? bilingual_str{} : wallet.GetError();
    

    w0xlt commented at 3:00 pm on July 16, 2022:

    @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 (review) and #25616 (review) suggested changing them to else m_error_message = res_wallet.GetError(); but I had forgotten to change it to restoreWallet(). https://github.com/bitcoin/bitcoin/commit/7cbfa1335bd89d5831e916672e57aa8601b12bb4 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.


    furszy commented at 6:07 pm on July 16, 2022:

    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.


    MarcoFalke commented at 10:50 am on July 18, 2022:
    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.

    furszy commented at 1:02 pm on July 18, 2022:
    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.

    MarcoFalke commented at 1:08 pm on July 18, 2022:
    Is it possible to load a wallet with an error and then load a wallet without error on the same walletcontroller?

    furszy commented at 2:18 am on July 23, 2022:

    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).


    jonatack commented at 8:47 am on August 5, 2022:
    0-        if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(wallet.ReleaseObj());
    1-        else m_error_message = wallet.GetError();
    2+        if (wallet) {
    3+            m_wallet_model = m_wallet_controller->getOrCreateWallet(wallet.ReleaseObj());
    4+        } else {
    5+            m_error_message = wallet.GetError();
    6+        }
    
  8. DrahtBot commented at 10:28 am on July 15, 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:

    • #25722 (refactor: Use util::Result class for wallet loading by ryanofsky)
    • #25656 (refactor: wallet: return util::Result from GetReservedDestination methods by theStack)

    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. w0xlt force-pushed on Jul 15, 2022
  10. in src/wallet/interfaces.cpp:553 in 7cbfa1335b outdated
    548@@ -549,8 +549,15 @@ class WalletLoaderImpl : public WalletLoader
    549     void stop() override { return StopWallets(m_context); }
    550     void setMockTime(int64_t time) override { return SetMockTime(time); }
    551 
    552+    //! Return a wallet interface object or an error wrapped in a BResult instance
    553+    BResult<std::unique_ptr<Wallet>> makeWallet(const std::shared_ptr<CWallet>& _wallet, bilingual_str& error)
    


    aureleoules commented at 10:29 am on July 18, 2022:
    can makeWallet be renamed to makeWalletResult or something else? Having both createWallet and makeWallet is a bit confusing.

  11. aureleoules commented at 10:32 am on July 18, 2022: member

    ACK 7cbfa1335bd89d5831e916672e57aa8601b12bb4. 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?

  12. shaavan commented at 1:30 pm on July 18, 2022: contributor

    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.
  13. w0xlt force-pushed on Jul 22, 2022
  14. w0xlt commented at 10:09 pm on July 22, 2022: contributor
    #25616#pullrequestreview-1041617804 and #25616#pullrequestreview-1041856598 were addressed in https://github.com/bitcoin/bitcoin/commit/087959d6e92ed37b72b86dbbc9b0ff052a6c5a75.
  15. shaavan approved
  16. shaavan commented at 7:58 am on July 23, 2022: contributor

    ACK 087959d6e92ed37b72b86dbbc9b0ff052a6c5a75

    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.

  17. in src/qt/walletcontroller.cpp:268 in 087959d6e9 outdated
    261@@ -262,9 +262,10 @@ void CreateWalletActivity::createWallet()
    262     }
    263 
    264     QTimer::singleShot(500ms, worker(), [this, name, flags] {
    265-        std::unique_ptr<interfaces::Wallet> wallet = node().walletLoader().createWallet(name, m_passphrase, flags, m_error_message, m_warning_message);
    266+        auto res_wallet{node().walletLoader().createWallet(name, m_passphrase, flags, m_warning_message)};
    267 
    268-        if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(wallet));
    269+        if (res_wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(res_wallet.ReleaseObj());
    270+        else m_error_message = res_wallet.GetError();
    


    jonatack commented at 8:46 am on August 5, 2022:
    0-        if (res_wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(res_wallet.ReleaseObj());
    1-        else m_error_message = res_wallet.GetError();
    2+        if (res_wallet) {
    3+            m_wallet_model = m_wallet_controller->getOrCreateWallet(res_wallet.ReleaseObj());
    4+        } else {
    5+            m_error_message = res_wallet.GetError();
    6+        }
    

    Any reason why you’ve changed the variable name from wallet to res_wallet here? It’s wallet in the identical code sections below.

  18. in src/qt/walletcontroller.cpp:399 in 087959d6e9 outdated
    394@@ -393,8 +395,8 @@ void RestoreWalletActivity::restore(const fs::path& backup_file, const std::stri
    395     QTimer::singleShot(0, worker(), [this, backup_file, wallet_name] {
    396         auto wallet{node().walletLoader().restoreWallet(backup_file, wallet_name, m_warning_message)};
    397 
    398-        m_error_message = wallet ? bilingual_str{} : wallet.GetError();
    399         if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(wallet.ReleaseObj());
    400+        else m_error_message = wallet.GetError();
    


    jonatack commented at 8:48 am on August 5, 2022:
    0-        if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(wallet.ReleaseObj());
    1-        else m_error_message = wallet.GetError();
    2+        if (wallet) {
    3+            m_wallet_model = m_wallet_controller->getOrCreateWallet(wallet.ReleaseObj());
    4+        } else {
    5+            m_error_message = wallet.GetError();
    6+        }
    
  19. jonatack commented at 9:08 am on August 5, 2022: contributor
    Approach ACK
  20. DrahtBot added the label Needs rebase on Aug 5, 2022
  21. w0xlt force-pushed on Aug 7, 2022
  22. w0xlt force-pushed on Aug 7, 2022
  23. w0xlt commented at 5:21 am on August 7, 2022: contributor

    Thanks for the review @shaavan, but a rebase was needed.

    Rebased and changed the code to use util::Result which replaced the BResult. @jonatack I used the suggested format in this new commit.

  24. DrahtBot removed the label Needs rebase on Aug 7, 2022
  25. in src/interfaces/wallet.h:91 in f179998f80 outdated
    87@@ -88,7 +88,7 @@ class Wallet
    88     virtual std::string getWalletName() = 0;
    89 
    90     // Get a new address.
    91-    virtual util::Result<CTxDestination> getNewDestination(const OutputType type, const std::string label) = 0;
    92+    virtual util::Result<CTxDestination> getNewDestination(const OutputType type, const std::string& label) = 0;
    


    jonatack commented at 2:39 pm on August 7, 2022:
    Thanks for picking up #25721 (review) here and fixing up indentation in this file.

    MarcoFalke commented at 5:03 pm on August 10, 2022:

    The wallet method still creates a copy?

    0$ git grep GetNewDestination -- '*wallet.h'
    1src/wallet/wallet.h:    util::Result<CTxDestination> GetNewDestination(const OutputType type, const std::string label);
    
  26. in src/wallet/interfaces.cpp:576 in f179998f80 outdated
    576         DatabaseStatus status;
    577         ReadDatabaseArgs(*m_context.args, options);
    578         options.require_existing = true;
    579-        return MakeWallet(m_context, LoadWallet(m_context, name, true /* load_on_start */, options, status, error, warnings));
    580+        bilingual_str error;
    581+        util::Result<std::unique_ptr<Wallet>> wallet{MakeWallet(m_context, LoadWallet(m_context, name, true /* load_on_start */, options, status, error, warnings))};
    


    jonatack commented at 2:43 pm on August 7, 2022:
    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.
  27. in src/wallet/interfaces.cpp:567 in f179998f80 outdated
    565+        bilingual_str error;
    566+        util::Result<std::unique_ptr<Wallet>> wallet{MakeWallet(m_context, CreateWallet(m_context, name, true /* load_on_start */, options, status, error, warnings))};
    567+        if (!wallet) {
    568+            return util::Error{error};
    569+        }
    570+        return wallet;
    


    jonatack commented at 2:47 pm on August 7, 2022:
    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)

    w0xlt commented at 8:05 pm on August 7, 2022:

    That would be good, but applying this change will result in the error below. I’m not sure about the reason for the error.

    0wallet/interfaces.cpp:577:25: error: call to implicitly-deleted copy constructor of 'util::Result<std::unique_ptr<Wallet>>'
    1        return wallet ? wallet : util::Error{error};
    2                        ^~~~~~
    3./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
    4    std::variant<bilingual_str, T> m_variant;
    

    jonatack commented at 9:03 pm on August 7, 2022:
    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};

    w0xlt commented at 1:26 am on August 8, 2022:

    Thanks. Done in https://github.com/bitcoin/bitcoin/pull/25616/commits/466374f6c1a032d7540b1c9307631f56fe555fcf.

    So return wallet moves the object and return wallet ? wallet : util::Error{error}; copies the object. Interesting.


    jonatack commented at 7:57 am on August 8, 2022:

    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.



    jonatack commented at 6:16 am on August 9, 2022:
    Thanks, not a blocker but this is a topic I plan to go deeper into in order to improve my understanding.
  28. jonatack commented at 2:50 pm on August 7, 2022: contributor
    Review/debug build/unit tests ACK f179998f8072ef5f754d42b5d3d7c14e2fdfe7a3
  29. theStack commented at 4:11 pm on August 7, 2022: contributor

    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/).

  30. w0xlt renamed this:
    refactor: Return `BResult` from WalletLoader methods
    refactor: Return `util::Result` from WalletLoader methods
    on Aug 7, 2022
  31. w0xlt force-pushed on Aug 8, 2022
  32. w0xlt commented at 1:22 am on August 8, 2022: contributor

    CI error seems unrelated. interface_usdt_validation.py worked fine on my machine.

    https://cirrus-ci.com/task/6722929781112832

  33. in .vscode/settings.json:1 in 466374f6c1 outdated
    0@@ -0,0 +1,81 @@
    1+{
    


    jonatack commented at 12:53 pm on August 8, 2022:
    Looks like this file invited itself into the last push, ACK 466374f6c1a032d7540b1c9307631f56fe555fcf otherwise :)

    w0xlt commented at 1:00 pm on August 8, 2022:
    Removed. Thanks.
  34. w0xlt force-pushed on Aug 8, 2022
  35. in src/wallet/test/wallet_tests.cpp:924 in be13477a82 outdated
    918@@ -919,10 +919,9 @@ BOOST_FIXTURE_TEST_CASE(wallet_sync_tx_invalid_state_test, TestingSetup)
    919     // Add tx to wallet
    920     const auto& op_dest = wallet.GetNewDestination(OutputType::BECH32M, "");
    921     BOOST_ASSERT(op_dest);
    922-    const CTxDestination& dest = *op_dest;
    923 
    924     CMutableTransaction mtx;
    925-    mtx.vout.push_back({COIN, GetScriptForDestination(dest)});
    926+    mtx.vout.push_back({COIN, GetScriptForDestination(*op_dest)});
    


    jonatack commented at 6:08 am on August 9, 2022:
    Thanks for picking up #25721 (review) here.
  36. in src/wallet/scriptpubkeyman.cpp:1773 in be13477a82 outdated
    1769@@ -1770,7 +1770,7 @@ bool DescriptorScriptPubKeyMan::GetReservedDestination(const OutputType type, bo
    1770     } else {
    1771         error = util::ErrorString(op_dest);
    1772     }
    1773-    return bool(op_dest);
    1774+    return op_dest.has_value();
    


    jonatack commented at 6:09 am on August 9, 2022:
    Thanks for picking up the suggestion #25721 (review) here.
  37. jonatack commented at 6:11 am on August 9, 2022: contributor
    ACK be13477a82e4dc33a88d49dcf3f7a296bcb6c05c
  38. in src/qt/walletcontroller.cpp:271 in be13477a82 outdated
    267 
    268-        if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(wallet));
    269+        m_error_message = util::ErrorString(wallet);
    270+        if (wallet) {
    271+            m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(*wallet));
    272+        }
    


    furszy commented at 2:11 pm on August 9, 2022:

    why the revert here? (and in the others as well). the result could be an object or an error, not both.

    0if (wallet) m_wallet_model = something;
    1else 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.


    w0xlt commented at 3:52 pm on August 9, 2022:

    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"


    furszy commented at 3:57 pm on August 9, 2022:
    yep, that was where I was pointing to. assert(!result.has_value()); there should work fine.

    w0xlt commented at 6:43 pm on August 9, 2022:

    I applied the suggestion above (if (wallet) m_wallet_model = something;) in https://github.com/bitcoin/bitcoin/pull/25616/commits/ac7479953bc95baf6ef4b35fa25c27dd4100fe6a.

    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.


    jonatack commented at 7:41 pm on August 9, 2022:
    Missing brackets in the conditionals again (e.g. #25616 (review))

    furszy commented at 7:45 pm on August 9, 2022:

    jonatack commented at 8:02 pm on August 9, 2022:
    “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.”

    furszy commented at 8:08 pm on August 9, 2022:

    yep, aren’t we having a single line statement here?

    0if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(*wallet));
    1else m_error_message = util::ErrorString(wallet);
    
  39. furszy commented at 2:13 pm on August 9, 2022: member
    utACK be13477a, just a small comment.
  40. w0xlt force-pushed on Aug 9, 2022
  41. furszy approved
  42. furszy commented at 6:38 pm on August 9, 2022: member
    ACK ac74799
  43. w0xlt force-pushed on Aug 10, 2022
  44. DrahtBot added the label Needs rebase on Aug 10, 2022
  45. wallet: Return `util::Result` from WalletLoader methods 07df6cda14
  46. w0xlt force-pushed on Aug 10, 2022
  47. w0xlt commented at 2:38 pm on August 10, 2022: contributor
    Rebased.
  48. jonatack commented at 3:17 pm on August 10, 2022: contributor
    Review ACK 07df6cda1468ed45ac227ac6f0169b040e5c0bf3
  49. DrahtBot removed the label Needs rebase on Aug 10, 2022
  50. theStack approved
  51. theStack commented at 3:28 pm on August 10, 2022: contributor
    Code-review ACK 07df6cda1468ed45ac227ac6f0169b040e5c0bf3
  52. MarcoFalke merged this on Aug 10, 2022
  53. MarcoFalke closed this on Aug 10, 2022

  54. in src/interfaces/wallet.h:323 in 07df6cda14
    319@@ -320,31 +320,31 @@ class WalletLoader : public ChainClient
    320 {
    321 public:
    322     //! Create new wallet.
    323-    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;
    324+    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;
    


    MarcoFalke commented at 5:12 pm on August 10, 2022:
    Would probably be best to also wrap the warnings in the result? Are you working on this @ryanofsky ?

    ryanofsky commented at 3:18 pm on August 15, 2022:

    re: #25616 (review)

    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


    MarcoFalke commented at 3:29 pm on August 15, 2022:
    Yeah, I meant a minimal extract without the other C++ bloat (for example Result<void>) that isn’t needed for simply passing warnings
  55. in src/qt/walletcontroller.cpp:397 in 07df6cda14
    400@@ -393,8 +401,11 @@ void RestoreWalletActivity::restore(const fs::path& backup_file, const std::stri
    401     QTimer::singleShot(0, worker(), [this, backup_file, wallet_name] {
    402         auto wallet{node().walletLoader().restoreWallet(backup_file, wallet_name, m_warning_message)};
    403 
    404-        m_error_message = util::ErrorString(wallet);
    405-        if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(*wallet));
    


    MarcoFalke commented at 5:12 pm on August 10, 2022:
    nit: I preferred the previous code, as it is shorter and less ambiguous
  56. w0xlt deleted the branch on Aug 10, 2022
  57. sidhujag referenced this in commit 8ba56bdfd0 on Aug 11, 2022
  58. bitcoin locked this on Aug 15, 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-11-17 06:12 UTC

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