{
 "number": 34844,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/34844",
 "title": "util: Add util::NotNull<SmartPtrType>",
 "author": "maflcko",
 "author_association": "MEMBER",
 "created_at": "2026-03-17T19:42:52Z",
 "updated_at": "2026-09-13T10:11:06Z",
 "age_days": 183,
 "draft": false,
 "labels": [
  "Utils/log/libs"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "fa0e12e77bb90bef05bfd8a960e1a28673b575be",
 "head_ref": "2603-not-null",
 "head_repo": "maflcko/bitcoin-core",
 "head_history": [
  {
   "t": "2026-03-17T20:03:08Z",
   "sha": "fa914242fa0436a62bd2ad2d5ce0ac228d38d2e3"
  },
  {
   "t": "2026-03-17T21:31:19Z",
   "sha": "fa9851dc607fe8ac031643cf4fc24faf43442584"
  },
  {
   "t": "2026-03-24T08:37:10Z",
   "sha": "faa7f087318e195b8f46f4fd0c08651b360ddf4e"
  },
  {
   "t": "2026-03-24T08:39:53Z",
   "sha": "fa70d413f8bd738a0eb07fdce2f59585a2c77011"
  },
  {
   "t": "2026-04-03T11:20:30Z",
   "sha": "fa877daaef64833640089a67645dc84ec3d4be3a"
  },
  {
   "t": "2026-04-21T14:35:04Z",
   "sha": "fabca57b2aae2f55dbe2ffb2cd77eb7c4098f0fc"
  },
  {
   "t": "2026-04-21T15:15:27Z",
   "sha": "fa6e1f4b55b88e505d2224331792939f4b1f7665"
  },
  {
   "t": "2026-04-23T06:02:25Z",
   "sha": "fa1d2b5469d0304ae7c1b3b0ce3936b45afd7247"
  },
  {
   "t": "2026-05-22T08:09:24Z",
   "sha": "77773e8c856fff45ad432edfe120eb13a6be95a6"
  },
  {
   "t": "2026-05-26T18:21:39Z",
   "sha": "fae0a346c1ea2c96160448f660b37749d695265e"
  },
  {
   "t": "2026-06-16T09:31:51Z",
   "sha": "fabc56dc9d1687a4a2a867d2c10a4a8fda4f73ff"
  },
  {
   "t": "2026-06-16T09:39:58Z",
   "sha": "fa7cd208fa15906dd6d1eee84a588227231dc912"
  },
  {
   "t": "2026-06-16T10:14:44Z",
   "sha": "6389aa8f15aa4ed55d76afda36c0b1c870f1c9d0"
  },
  {
   "t": "2026-06-16T17:48:17Z",
   "sha": "fa951b422c43d7a3b51aaefbaf25f3d9d53e1a69"
  },
  {
   "t": "2026-07-09T11:23:55Z",
   "sha": "fa53bbcb112b5fabf12a0f02622bc890a3bcd5fb"
  },
  {
   "t": "2026-07-17T10:32:38Z",
   "sha": "fa3faffc76e916b162432ac7e1740761155ffeec"
  },
  {
   "t": "2026-07-17T10:53:39Z",
   "sha": "fac8d22952dcd623b6e5860bf91a8bea85877c49"
  },
  {
   "t": "2026-07-17T11:07:19Z",
   "sha": "fa0227405c497e94ddb7a7603b37993a7703c0c1"
  },
  {
   "t": "2026-07-22T08:38:33Z",
   "sha": "fa478f9ce2e2ca44df2db2ae9fe2276729f4adb5"
  },
  {
   "t": "2026-07-22T08:49:10Z",
   "sha": "fa6228d332063f3345925ede050d61480a09aab6"
  },
  {
   "t": "2026-08-02T13:52:29Z",
   "sha": "fa0e12e77bb90bef05bfd8a960e1a28673b575be"
  }
 ],
 "additions": 380,
 "deletions": 65,
 "changed_files": 13,
 "commit_count": 5,
 "size_bucket": "L",
 "mergeable_state": "clean",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "ack": [
     {
      "login": "stickies-v",
      "url": "https://github.com/bitcoin/bitcoin/pull/34844#pullrequestreview-4846732847"
     }
    ],
    "concept_ack": [
     {
      "login": "sedited",
      "url": "https://github.com/bitcoin/bitcoin/pull/34844#issuecomment-4762066778"
     }
    ],
    "stale_ack": [
     {
      "login": "l0rinc",
      "url": "https://github.com/bitcoin/bitcoin/pull/34844#issuecomment-4727802853"
     }
    ]
   },
   "conflicts": [
    {
     "number": 36014,
     "title": "init: ignore repeated `-addnode` startup values",
     "author": "w0xlt"
    },
    {
     "number": 35557,
     "title": "kernel, validation: Add btck_chainstate_manager_set_clock_time",
     "author": "ryanofsky"
    },
    {
     "number": 35511,
     "title": "RFC: consensus: Make `CAmount` a class",
     "author": "hodlinator"
    },
    {
     "number": 34132,
     "title": "coins, dbwrapper: remove error catcher, make point-read failures fatal",
     "author": "l0rinc"
    }
   ]
  }
 },
 "acks_parsed": {
  "stickies-v": {
   "kind": "ack",
   "hash": "fa0e12e77bb90bef05bfd8a960e1a28673b575be",
   "t": "2026-08-03T17:07:47Z",
   "stale": false
  },
  "l0rinc": {
   "kind": "ack",
   "hash": "fae0a346c1ea2c96160448f660b37749d695265e",
   "t": "2026-06-02T10:55:53Z",
   "stale": true
  },
  "sedited": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-06-21T13:05:20Z",
   "stale": false
  }
 },
 "acks_tally": {
  "ack": 1,
  "stale_ack": 1,
  "concept_ack": 1,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 2,
  "changes_requested": 1,
  "distinct_reviewers": [
   "l0rinc",
   "optout21",
   "sedited",
   "stickies-v"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-08-03T09:56:26Z",
  "last_reviewer_activity": "2026-08-03T17:07:47Z",
  "last_reviewer": "stickies-v",
  "author_silent_days": 45,
  "waiting_on_author_days": 44,
  "days_since_update": 4
 },
 "refs": {
  "mentioned": [
   35275,
   35461
  ],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [
   {
    "number": 35275,
    "type": "issue",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "scripted-diff: structural risk in the eval-based verifier"
   },
   {
    "number": 35461,
    "type": "pull",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "util: Clarify the assertion message in assertion failures (Assert, Assume, etc.)"
   }
  ],
  "conflicts": [
   36014,
   35557,
   35511,
   34132
  ]
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "src/dbwrapper.h",
  "src/net.cpp",
  "src/test/CMakeLists.txt",
  "src/test/rbf_tests.cpp",
  "src/test/util_pointers_tests.cpp",
  "src/util/pointers.h"
 ],
 "body": "The C++ standard library lacks a type to denote a smart pointer is not null. For raw pointer there is `std::reference_wrapper`, or a plain reference.\n\nOther third-party libraries provide such a type, such as `gsl::strict_not_null`.\n\nFix all issues by adding `util::NotNull<SmartPtrType>`, which documents (and checks) that the inner pointer is never null.\n\nThis type can be used when passing never-null smart pointers between functions. It removes the need to `Assert()` the pointer before dereference. For example, in a getter function:\n\n```cpp\nutil::NotNull<std::unique_ptr<Stats>> GetStats()\n{\n    return util::NotNull{std::make_unique<Stats>()};\n}\n\nint main()\n{\n    auto stats{GetStats()};\n    stats->foo; // This can never lead to a nullptr deref\n    // Assert(stats)->foo; // This is redundant and won't compile\n}\n```\n\nFixes https://github.com/bitcoin/bitcoin/issues/24423",
 "commits": [
  {
   "sha": "fa3bda5fb80d93bf71a01a903f70dd6598c04199",
   "date": "2026-08-02T13:42:43Z",
   "message": "util: Add util::NotNull<SmartPtrType>\n\nCo-Authored-By: stickies-v <stickies-v@protonmail.com>"
  },
  {
   "sha": "fac1aaacf67ab11d054840d1676c575257e803b1",
   "date": "2026-08-02T13:43:30Z",
   "message": "refactor: Use util::NotNull<std::unique_ptr<LevelDBContext>> m_db_context\n\nThis allows to drop the runtime Assert every time the context is\naccessed, because the type is known to be not null at compile-time.\n\nNote there is still the runtime Assert inside the util::NotNull\nconstructor itself. However, this is called at most once, with the\npossibility of the compiler being able to optimize it away."
  },
  {
   "sha": "fae7b4c2754d43e1432eecaa80ecdb7e6d8e9f46",
   "date": "2026-08-02T13:43:33Z",
   "message": "refactor: Use NotNull pointer to input fetching pool"
  },
  {
   "sha": "fa1c90333fb6a72a2af8ec334c3fb8feeae81850",
   "date": "2026-08-02T13:47:24Z",
   "message": "refactor: In CNode use util::NotNull<std::unique_ptr<Transport>> m_transport\n\nThis documents (and checks) that the transport pointer is never null."
  },
  {
   "sha": "fa0e12e77bb90bef05bfd8a960e1a28673b575be",
   "date": "2026-08-02T13:47:24Z",
   "message": "refactor: Return util::NotNull<std::unique_ptr<ChangeSet>> from CTxMemPool::GetChangeSet()\n\nThis documents (and checks) that the change set is never null.\n\nThis also nudges the test code to be cleaner, because the changeset is\nnot manually re-set and re-used over a large scope with several sub-test\ncases. Instead each change set for each sub-test case is in a small and\ndedicated scope.\n\nCan be reviewed via --ignore-all-space"
  }
 ],
 "timeline": [
  {
   "t": "2026-03-17T20:03:08Z",
   "kind": "force_push",
   "who": "maflcko",
   "commit": "fa914242fa0436a62bd2ad2d5ce0ac228d38d2e3"
  },
  {
   "t": "2026-03-17T20:08:37Z",
   "kind": "comment",
   "who": "stickies-v",
   "assoc": "MEMBER",
   "text": "Concept ACK"
  },
  {
   "t": "2026-03-17T21:31:19Z",
   "kind": "force_push",
   "who": "maflcko",
   "commit": "fa9851dc607fe8ac031643cf4fc24faf43442584"
  },
  {
   "t": "2026-03-24T08:37:10Z",
   "kind": "force_push",
   "who": "maflcko",
   "commit": "faa7f087318e195b8f46f4fd0c08651b360ddf4e"
  },
  {
   "t": "2026-03-24T08:39:53Z",
   "kind": "force_push",
   "who": "maflcko",
   "commit": "fa70d413f8bd738a0eb07fdce2f59585a2c77011"
  },
  {
   "t": "2026-04-03T11:20:30Z",
   "kind": "force_push",
   "who": "maflcko",
   "commit": "fa877daaef64833640089a67645dc84ec3d4be3a"
  },
  {
   "t": "2026-04-17T11:38:44Z",
   "kind": "comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "text": "Probably not going to push here, but another place where this would be suitable is `GetWalletForJSONRPCRequest`\n\nEdit: diff in https://github.com/bitcoin/bitcoin/pull/34844#discussion_r3305943200\n\nOther suggested diffs: https://github.com/bitcoin/bitcoin/pull/34844#discussion_r3293173669\n\nedit: or even https://www.github.com/bitcoin/bitcoin/pull/35569#issuecomment-4835740603\n\nedit: or add annotations/attributes, see https://github.com/bitcoin/bitcoin/pull/34844#discussion_r3702941931"
  },
  {
   "t": "2026-04-21T14:35:04Z",
   "kind": "force_push",
   "who": "maflcko",
   "commit": "fabca57b2aae2f55dbe2ffb2cd77eb7c4098f0fc"
  },
  {
   "t": "2026-04-21T15:15:27Z",
   "kind": "force_push",
   "who": "maflcko",
   "commit": "fa6e1f4b55b88e505d2224331792939f4b1f7665"
  },
  {
   "t": "2026-04-21T16:28:26Z",
   "kind": "comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "text": "Removed the movable feature of NotNull for now, which can be added/reviewed later\n\nedit: added this back again"
  },
  {
   "t": "2026-04-23T06:02:25Z",
   "kind": "force_push",
   "who": "maflcko",
   "commit": "fa1d2b5469d0304ae7c1b3b0ce3936b45afd7247"
  },
  {
   "t": "2026-05-22T08:09:24Z",
   "kind": "force_push",
   "who": "maflcko",
   "commit": "77773e8c856fff45ad432edfe120eb13a6be95a6"
  },
  {
   "t": "2026-05-23T10:40:30Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/test/util_pointers_tests.cpp",
   "commit": "fa1e0bb7d0e132b880e850cf8666abc3a5a6e926",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nnit: This failure reason is a bit hard to read: `ptr_ != nullptr` is the invariant that failed, so this test is actually checking the null case. In other words, the message prints the failed condition, not the observed value. Likely not something we can fix here..."
  },
  {
   "t": "2026-05-23T11:10:11Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/dbwrapper.h",
   "commit": "faef857079bd2467e2d04cbcd814b0b36e9fb7b0",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nCould we avoid repeating the verbose template instantiation for all non-null smart pointers? I like the extra guarantee, but I don't like how verbose the results are.\nWe could add `NotNullUniquePtr` and `NotNullSharedPtr` aliases and demo them at the mentioned call sites:\n```patch\ndiff --git a/src/dbwrapper.h b/src/dbwrapper.h\nindex 4c0b6e2629..46ce177c5c 100644\n--- a/src/dbwrapper.h\n+++ b/src/dbwrapper.h\n@@ -189,7 +189,7 @@ class CDBWrapper\n     friend const Obfuscation& dbwrapper_private::GetObfuscation(const CDBWrapper&);\n private:\n     //! holds all leveldb-specific fields of this class\n-    util::NotNull<std::unique_ptr<LevelDBContext>> m_db_context;\n+    util::NotNullUniquePtr<LevelDBContext> m_db_context;\n\n     //! the name of this database\n     std::string m_name;\ndiff --git a/src/net.cpp b/src/net.cpp\nindex f4c5dbd157..fab392814e 100644\n--- a/src/net.cpp\n+++ b/src/net.cpp\n@@ -4004,12 +4004,12 @@ ServiceFlags CConnman::GetLocalServices() const\n     return m_local_services;\n }\n\n-static util::NotNull<std::unique_ptr<Transport>> MakeTransport(NodeId id, bool use_v2transport, bool inbound) noexcept\n+static util::NotNullUniquePtr<Transport> MakeTransport(NodeId id, bool use_v2transport, bool inbound) noexcept\n {\n     if (use_v2transport) {\n-        return util::NotNull<std::unique_ptr<Transport>>{std::make_unique<V2Transport>(id, /*initiating=*/!inbound)};\n+        return util::NotNullUniquePtr<Transport>{std::make_unique<V2Transport>(id, /*initiating=*/!inbound)};\n     } else {\n-        return util::NotNull<std::unique_ptr<Transport>>{std::make_unique<V1Transport>(id)};\n+        return util::NotNullUniquePtr<Transport>{std::make_unique<V1Transport>(id)};\n     }\n }\n\ndiff --git a/src/net.h b/src/net.h\nindex e6a680e048..0dbb692149 100644\n--- a/src/net.h\n+++ b/src/net.h\n@@ -682,7 +682,7 @@ class CNode\n public:\n     /** Transport serializer/deserializer. The receive side functions are only called under cs_vRecv, while\n      * the sending side functions are only called under cs_vSend. */\n-    const util::NotNull<std::unique_ptr<Transport>> m_transport;\n+    const util::NotNullUniquePtr<Transport> m_transport;\n\n     const NetPermissionFlags m_permission_flags;\n\ndiff --git a/src/test/util_pointers_tests.cpp b/src/test/util_pointers_tests.cpp\nindex 5d77e89e15..38c49a5cd6 100644\n--- a/src/test/util_pointers_tests.cpp\n+++ b/src/test/util_pointers_tests.cpp\n@@ -7,9 +7,14 @@\n\n #include <boost/test/unit_test.hpp>\n\n+#include <memory>\n #include <set>\n+#include <type_traits>\n #include <unordered_set>\n\n+static_assert(std::is_same_v<util::NotNullUniquePtr<int>, util::NotNull<std::unique_ptr<int>>>);\n+static_assert(std::is_same_v<util::NotNullSharedPtr<int>, util::NotNull<std::shared_ptr<int>>>);\n+\n BOOST_AUTO_TEST_SUITE(util_pointers_tests)\n\n BOOST_AUTO_TEST_CASE(check_nullptr)\ndiff --git a/src/util/pointers.h b/src/util/pointers.h\nindex 08af3c1b28..e757c6012b 100644\n--- a/src/util/pointers.h\n+++ b/src/util/pointers.h\n@@ -16,6 +16,8 @@\n //    - strict_make_not_null, because it is not needed.\n // * Remove the not_null->strict_not_null converting constructors, because they\n //   are not needed.\n+// * Add NotNullUniquePtr and NotNullSharedPtr aliases to keep smart-pointer\n+//   call sites readable.\n //\n // All original code is covered by:\n\n@@ -357,6 +359,12 @@ struct NotNull : public gsl_detail::strict_not_null<T> {\n template <typename T>\n NotNull(T) -> NotNull<T>;\n\n+template <typename T, typename Deleter = std::default_delete<T>>\n+using NotNullUniquePtr = NotNull<std::unique_ptr<T, Deleter>>;\n+\n+template <typename T>\n+using NotNullSharedPtr = NotNull<std::shared_ptr<T>>;\n+\n } // namespace util\n\n namespace std\n```"
  },
  {
   "t": "2026-05-23T11:17:45Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/util/pointers.h",
   "commit": "fabc56dc9d1687a4a2a867d2c10a4a8fda4f73ff",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nThe `noexcept` annotations are a bit confusing since that's exactly how we're testing the methods - `check_nullptr` works because it deliberately avoids the `noexcept` path.\nI understand that `test_only_CheckFailuresAreExceptionsNotAborts` isn't used in prod and we don't like modifying prod for tests, but we do have a failure case inside and I'm just not sure what happens if we are explicit about not throwing while deliberately relying on throwing behavior (currently this terminates before the test can observe `NonFatalCheckError`. Maybe that is acceptable for production, but it makes the main smart-pointer null checks harder to test).\n\nThis way we can't test for example:\n```C++\nBOOST_AUTO_TEST_CASE(check_null_smart_pointer)\n{\n    test_only_CheckFailuresAreExceptionsNotAborts mock_checks{};\n\n    BOOST_CHECK_THROW(util::NotNull{std::unique_ptr<int>{}}, NonFatalCheckError);\n    BOOST_CHECK_THROW(util::NotNull{std::shared_ptr<int>{}}, NonFatalCheckError);\n}\n```\nit just fails with\n[quoted text omitted]\n\nBut without the `noexcept` the above passes:\n```patch\ndiff --git a/src/util/pointers.h b/src/util/pointers.h\nindex f985c5331b..28dd458f58 100644\n--- a/src/util/pointers.h\n+++ b/src/util/pointers.h\n@@ -116,19 +116,19 @@ public:\n     using element_type = T;\n\n     template <typename U, typename = std::enable_if_t<std::is_convertible<U, T>::value>>\n-    constexpr not_null(U&& u) noexcept(std::is_nothrow_move_constructible<T>::value) : ptr_(std::forward<U>(u))\n+    constexpr not_null(U&& u) : ptr_(std::forward<U>(u))\n     {\n         Assert(ptr_ != nullptr);\n     }\n\n     template <typename = std::enable_if_t<!std::is_same<std::nullptr_t, T>::value>>\n-    constexpr not_null(T u) noexcept(std::is_nothrow_move_constructible<T>::value) : ptr_(std::move(u))\n+    constexpr not_null(T u) : ptr_(std::move(u))\n     {\n         Assert(ptr_ != nullptr);\n     }\n\n     template <typename U, typename = std::enable_if_t<std::is_convertible<U, T>::value>>\n-    constexpr not_null(const not_null<U>& other) noexcept(std::is_nothrow_move_constructible<T>::value) : not_null(other.get())\n+    constexpr not_null(const not_null<U>& other) : not_null(other.get())\n     {}\n\n     not_null(const not_null& other) = default;\n@@ -169,7 +169,7 @@ void swap(not_null<T>& a, not_null<T>& b) noexcept\n }\n\n template <class T>\n-auto make_not_null(T&& t) noexcept\n+auto make_not_null(T&& t)\n {\n     return not_null<std::remove_cv_t<std::remove_reference_t<T>>>{std::forward<T>(t)};\n }\n@@ -284,15 +284,15 @@ class strict_not_null : public not_null<T>\n {\n public:\n     template <typename U, typename = std::enable_if_t<std::is_convertible<U, T>::value>>\n-    constexpr explicit strict_not_null(U&& u) noexcept(std::is_nothrow_move_constructible<T>::value) : not_null<T>(std::forward<U>(u))\n+    constexpr explicit strict_not_null(U&& u) : not_null<T>(std::forward<U>(u))\n     {}\n\n     template <typename = std::enable_if_t<!std::is_same<std::nullptr_t, T>::value>>\n-    constexpr explicit strict_not_null(T u) noexcept(std::is_nothrow_move_constructible<T>::value) : not_null<T>(std::move(u))\n+    constexpr explicit strict_not_null(T u) : not_null<T>(std::move(u))\n     {}\n\n     template <typename U, typename = std::enable_if_t<std::is_convertible<U, T>::value>>\n-    constexpr strict_not_null(const strict_not_null<U>& other) noexcept(std::is_nothrow_move_constructible<T>::value) : not_null<T>(other)\n+    constexpr strict_not_null(const strict_not_null<U>& other) : not_null<T>(other)\n     {}\n\n     // To avoid invalidating the \"not null\" invariant, the contained pointer is actually copied\n@@ -326,7 +326,7 @@ template <class T>\n strict_not_null<T> operator+(std::ptrdiff_t, const strict_not_null<T>&) = delete;\n\n template <class T>\n-auto make_strict_not_null(T&& t) noexcept\n+auto make_strict_not_null(T&& t)\n {\n     return strict_not_null<std::remove_cv_t<std::remove_reference_t<T>>>{std::forward<T>(t)};\n }\n```"
  },
  {
   "t": "2026-05-23T11:48:12Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/net.cpp",
   "commit": "77773e8c856fff45ad432edfe120eb13a6be95a6",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nThis extra verbosity is a bit of a turn-off. Can we add a forwarding constructor for class-type pointer wrappers (diff assumes the previous `NotNullUniquePtr` suggestion)?\n\n```patch\ndiff --git a/src/net.cpp b/src/net.cpp\nindex fab392814e..aaf946f4ab 100644\n--- a/src/net.cpp\n+++ b/src/net.cpp\n@@ -4007,9 +4007,9 @@ ServiceFlags CConnman::GetLocalServices() const\n static util::NotNullUniquePtr<Transport> MakeTransport(NodeId id, bool use_v2transport, bool inbound) noexcept\n {\n     if (use_v2transport) {\n-        return util::NotNullUniquePtr<Transport>{std::make_unique<V2Transport>(id, /*initiating=*/!inbound)};\n+        return std::make_unique<V2Transport>(id, /*initiating=*/!inbound);\n     } else {\n-        return util::NotNullUniquePtr<Transport>{std::make_unique<V1Transport>(id)};\n+        return std::make_unique<V1Transport>(id);\n     }\n }\n\ndiff --git a/src/test/util_pointers_tests.cpp b/src/test/util_pointers_tests.cpp\nindex 38c49a5cd6..85807f8e73 100644\n--- a/src/test/util_pointers_tests.cpp\n+++ b/src/test/util_pointers_tests.cpp\n@@ -63,6 +63,7 @@ BOOST_AUTO_TEST_CASE(check_swap)\n BOOST_AUTO_TEST_CASE(check_deref)\n {\n     int v{2};\n+    static_assert(!std::is_convertible_v<int*, util::NotNull<int*>>); // Keep raw-pointer NotNull construction explicit.\n     util::NotNull p(&v);\n     *p = 3;\n     BOOST_CHECK_EQUAL(v, 3);\ndiff --git a/src/util/pointers.h b/src/util/pointers.h\nindex e757c6012b..73bb45e3e7 100644\n--- a/src/util/pointers.h\n+++ b/src/util/pointers.h\n@@ -18,6 +18,7 @@\n //   are not needed.\n // * Add NotNullUniquePtr and NotNullSharedPtr aliases to keep smart-pointer\n //   call sites readable.\n+// * Add a forwarding constructor for concise non-null smart-pointer returns.\n //\n // All original code is covered by:\n\n@@ -353,8 +354,13 @@ struct hash<gsl_detail::strict_not_null<T>> : gsl_detail::not_null_hash<gsl_deta\n namespace util {\n\n template <class T>\n-struct NotNull : public gsl_detail::strict_not_null<T> {\n-    using gsl_detail::strict_not_null<T>::strict_not_null;\n+struct NotNull : gsl_detail::strict_not_null<T> {\n+    using Base = gsl_detail::strict_not_null<T>;\n+    using Base::Base;\n+\n+    template <typename U>\n+    requires (!std::is_pointer_v<T> && std::is_convertible_v<U, T>)\n+    constexpr NotNull(U&& u) : Base{std::forward<U>(u)} {}\n };\n template <typename T>\n NotNull(T) -> NotNull<T>;\n```\n\n---\n\nWhich obviously begs the question: do we ever want to convert back.\nGiven your hint for converting [GetWalletForJSONRPCRequest](https://github.com/bitcoin/bitcoin/pull/34844#issuecomment-4267662343) it may be necessary (unless we propagate the type further), but I agree with you that this could also be done in a followup.\n\nMigrate `GetWalletForJSONRPCRequest` to `util::NotNullSharedPtr<CWallet>`\n\n```patch\ndiff --git a/src/util/pointers.h b/src/util/pointers.h\nindex 8f740472a3..5826e21cdc 100644\n--- a/src/util/pointers.h\n+++ b/src/util/pointers.h\n@@ -21,6 +21,8 @@\n // * Add a forwarding constructor for concise non-null smart-pointer returns.\n // * Delete util::NotNull moves so the wrapper does not advertise misleading\n //   move operations.\n+// * Add compatible shared_ptr conversions so NotNullSharedPtr<T> can initialize\n+//   std::shared_ptr<const T> callers without rebuilding the handle.\n //\n // All original code is covered by:\n\n@@ -78,6 +80,12 @@ namespace details\n                                             const T,\n                                             const T&>;\n\n+    template <typename T>\n+    struct is_shared_ptr : std::false_type {};\n+\n+    template <typename T>\n+    struct is_shared_ptr<std::shared_ptr<T>> : std::true_type {};\n+\n } // namespace details\n\n //\n@@ -380,6 +388,10 @@ struct NotNull : gsl_detail::strict_not_null<T> {\n     // underlying smart pointer at transfer boundaries.\n     NotNull(NotNull&&) = delete;\n     NotNull& operator=(NotNull&&) = delete;\n+\n+    template <typename U>\n+        requires (!std::is_same_v<U, T> && gsl_detail::details::is_shared_ptr<T>::value && gsl_detail::details::is_shared_ptr<U>::value && std::is_convertible_v<T, U>)\n+    constexpr operator U() const { return this->get(); }\n };\n template <typename T>\n NotNull(T) -> NotNull<T>;\ndiff --git a/src/wallet/rpc/addresses.cpp b/src/wallet/rpc/addresses.cpp\nindex ed966d8944..9082407d6d 100644\n--- a/src/wallet/rpc/addresses.cpp\n+++ b/src/wallet/rpc/addresses.cpp\n@@ -39,7 +39,6 @@ RPCMethod getnewaddress()\n         [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n {\n     std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n\n     LOCK(pwallet->cs_wallet);\n\n@@ -88,7 +87,6 @@ RPCMethod getrawchangeaddress()\n         [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n {\n     std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n\n     LOCK(pwallet->cs_wallet);\n\n@@ -132,7 +130,6 @@ RPCMethod setlabel()\n         [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n {\n     std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n\n     LOCK(pwallet->cs_wallet);\n\n@@ -183,7 +180,6 @@ RPCMethod listaddressgroupings()\n         [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n {\n     const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n\n     // Make sure the results are valid at least up to the most recent block\n     // the user could have gotten from another RPC command prior to now\n@@ -232,7 +228,6 @@ RPCMethod keypoolrefill()\n         [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n {\n     std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n\n     LOCK(pwallet->cs_wallet);\n\n@@ -423,7 +418,6 @@ RPCMethod getaddressinfo()\n         [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n {\n     const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n\n     LOCK(pwallet->cs_wallet);\n\n@@ -536,7 +530,6 @@ RPCMethod getaddressesbylabel()\n         [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n {\n     const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n\n     LOCK(pwallet->cs_wallet);\n\n@@ -600,7 +593,6 @@ RPCMethod listlabels()\n         [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n {\n     const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n\n     LOCK(pwallet->cs_wallet);\n\n@@ -648,7 +640,6 @@ RPCMethod walletdisplayaddress()\n         [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n         {\n             std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);\n-            if (!wallet) return UniValue::VNULL;\n             CWallet* const pwallet = wallet.get();\n\n             LOCK(pwallet->cs_wallet);\ndiff --git a/src/wallet/rpc/backup.cpp b/src/wallet/rpc/backup.cpp\nindex 396be62825..a1cc43613c 100644\n--- a/src/wallet/rpc/backup.cpp\n+++ b/src/wallet/rpc/backup.cpp\n@@ -50,7 +50,6 @@ RPCMethod importprunedfunds()\n         [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n {\n     std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n\n     CMutableTransaction tx;\n     if (!DecodeHexTx(tx, request.params[0].get_str())) {\n@@ -108,7 +107,6 @@ RPCMethod removeprunedfunds()\n         [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n {\n     std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n\n     LOCK(pwallet->cs_wallet);\n\n@@ -377,7 +375,6 @@ RPCMethod importdescriptors()\n         [](const RPCMethod& self, const JSONRPCRequest& main_request) -> UniValue\n {\n     std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(main_request);\n-    if (!pwallet) return UniValue::VNULL;\n     CWallet& wallet{*pwallet};\n\n     // Make sure the results are valid at least up to the most recent block\n@@ -515,7 +512,6 @@ RPCMethod listdescriptors()\n         [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n {\n     const std::shared_ptr<const CWallet> wallet = GetWalletForJSONRPCRequest(request);\n-    if (!wallet) return UniValue::VNULL;\n\n     const bool priv = !request.params[0].isNull() && request.params[0].get_bool();\n     if (wallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && priv) {\n@@ -608,7 +604,6 @@ RPCMethod backupwallet()\n         [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n {\n     const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n\n     // Make sure the results are valid at least up to the most recent block\n     // the user could have gotten from another RPC command prior to now\ndiff --git a/src/wallet/rpc/coins.cpp b/src/wallet/rpc/coins.cpp\nindex ab869b0d3f..8eadaf1ded 100644\n--- a/src/wallet/rpc/coins.cpp\n+++ b/src/wallet/rpc/coins.cpp\n@@ -105,7 +105,6 @@ RPCMethod getreceivedbyaddress()\n         [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n {\n     const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n\n     // Make sure the results are valid at least up to the most recent block\n     // the user could have gotten from another RPC command prior to now\n@@ -147,7 +146,6 @@ RPCMethod getreceivedbylabel()\n         [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n {\n     const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n\n     // Make sure the results are valid at least up to the most recent block\n     // the user could have gotten from another RPC command prior to now\n@@ -188,7 +186,6 @@ RPCMethod getbalance()\n         [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n {\n     const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n\n     // Make sure the results are valid at least up to the most recent block\n     // the user could have gotten from another RPC command prior to now\n@@ -258,7 +255,6 @@ RPCMethod lockunspent()\n         [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n {\n     std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n\n     // Make sure the results are valid at least up to the most recent block\n     // the user could have gotten from another RPC command prior to now\n@@ -376,7 +372,6 @@ RPCMethod listlockunspent()\n         [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n {\n     const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n\n     LOCK(pwallet->cs_wallet);\n\n@@ -424,7 +419,6 @@ RPCMethod getbalances()\n         [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n {\n     const std::shared_ptr<const CWallet> rpc_wallet = GetWalletForJSONRPCRequest(request);\n-    if (!rpc_wallet) return UniValue::VNULL;\n     const CWallet& wallet = *rpc_wallet;\n\n     // Make sure the results are valid at least up to the most recent block\n@@ -520,7 +514,6 @@ RPCMethod listunspent()\n         [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n {\n     const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n\n     int nMinDepth = 1;\n     if (!request.params[0].isNull()) {\ndiff --git a/src/wallet/rpc/encrypt.cpp b/src/wallet/rpc/encrypt.cpp\nindex 68a80eb80e..ee2327ab98 100644\n--- a/src/wallet/rpc/encrypt.cpp\n+++ b/src/wallet/rpc/encrypt.cpp\n@@ -35,7 +35,6 @@ RPCMethod walletpassphrase()\n         [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n {\n     std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);\n-    if (!wallet) return UniValue::VNULL;\n     CWallet* const pwallet = wallet.get();\n\n     int64_t nSleepTime;\n@@ -132,7 +131,6 @@ RPCMethod walletpassphrasechange()\n         [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n {\n     std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n\n     if (!pwallet->HasEncryptionKeys()) {\n         throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, \"Error: running with an unencrypted wallet, but walletpassphrasechange was called.\");\n@@ -197,7 +195,6 @@ RPCMethod walletlock()\n         [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n {\n     std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n\n     if (!pwallet->HasEncryptionKeys()) {\n         throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, \"Error: running with an unencrypted wallet, but walletlock was called.\");\n@@ -250,7 +247,6 @@ RPCMethod encryptwallet()\n         [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n {\n     std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n\n     if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {\n         throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, \"Error: wallet does not contain private keys, nothing to encrypt.\");\ndiff --git a/src/wallet/rpc/signmessage.cpp b/src/wallet/rpc/signmessage.cpp\nindex bd49f3e393..9073bf5762 100644\n--- a/src/wallet/rpc/signmessage.cpp\n+++ b/src/wallet/rpc/signmessage.cpp\n@@ -37,7 +37,6 @@ RPCMethod signmessage()\n         [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n         {\n             const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);\n-            if (!pwallet) return UniValue::VNULL;\n\n             LOCK(pwallet->cs_wallet);\n\ndiff --git a/src/wallet/rpc/spend.cpp b/src/wallet/rpc/spend.cpp\nindex b6cdc8600f..e90e0772fa 100644\n--- a/src/wallet/rpc/spend.cpp\n+++ b/src/wallet/rpc/spend.cpp\n@@ -288,7 +288,6 @@ RPCMethod sendtoaddress()\n         [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n {\n     std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n\n     // Make sure the results are valid at least up to the most recent block\n     // the user could have gotten from another RPC command prior to now\n@@ -392,7 +391,6 @@ RPCMethod sendmany()\n         [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n {\n     std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n\n     // Make sure the results are valid at least up to the most recent block\n     // the user could have gotten from another RPC command prior to now\n@@ -799,7 +797,6 @@ RPCMethod fundrawtransaction()\n         [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n {\n     std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n\n     // parse hex string from parameter\n     CMutableTransaction tx;\n@@ -900,7 +897,6 @@ RPCMethod signrawtransactionwithwallet()\n         [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n {\n     const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n\n     CMutableTransaction mtx;\n     if (!DecodeHexTx(mtx, request.params[0].get_str())) {\n@@ -1033,7 +1029,6 @@ static RPCMethod bumpfee_helper(std::string method_name)\n         [want_psbt](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n {\n     std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n\n     if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !pwallet->IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER) && !want_psbt) {\n         throw JSONRPCError(RPC_WALLET_ERROR, \"bumpfee is not available with wallets that have private keys disabled. Use psbtbumpfee instead.\");\n@@ -1263,7 +1258,6 @@ RPCMethod send()\n         [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n         {\n             std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);\n-            if (!pwallet) return UniValue::VNULL;\n\n             UniValue options{request.params[4].isNull() ? UniValue::VOBJ : request.params[4]};\n             InterpretFeeEstimationInstructions(/*conf_target=*/request.params[1], /*estimate_mode=*/request.params[2], /*fee_rate=*/request.params[3], options);\n@@ -1377,7 +1371,6 @@ RPCMethod sendall()\n         [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n         {\n             std::shared_ptr<CWallet> const pwallet{GetWalletForJSONRPCRequest(request)};\n-            if (!pwallet) return UniValue::VNULL;\n             // Make sure the results are valid at least up to the most recent block\n             // the user could have gotten from another RPC command prior to now\n             pwallet->BlockUntilSyncedToCurrentChain();\n@@ -1623,7 +1616,6 @@ RPCMethod walletprocesspsbt()\n         [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n {\n     const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n\n     const CWallet& wallet{*pwallet};\n     // Make sure the results are valid at least up to the most recent block\n@@ -1755,7 +1747,6 @@ RPCMethod walletcreatefundedpsbt()\n         [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n {\n     std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n\n     CWallet& wallet{*pwallet};\n     // Make sure the results are valid at least up to the most recent block\ndiff --git a/src/wallet/rpc/transactions.cpp b/src/wallet/rpc/transactions.cpp\nindex 038e30fceb..784f8c2f92 100644\n--- a/src/wallet/rpc/transactions.cpp\n+++ b/src/wallet/rpc/transactions.cpp\n@@ -225,7 +225,6 @@ RPCMethod listreceivedbyaddress()\n         [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n {\n     const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n\n     // Make sure the results are valid at least up to the most recent block\n     // the user could have gotten from another RPC command prior to now\n@@ -270,7 +269,6 @@ RPCMethod listreceivedbylabel()\n         [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n {\n     const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n\n     // Make sure the results are valid at least up to the most recent block\n     // the user could have gotten from another RPC command prior to now\n@@ -466,7 +464,6 @@ RPCMethod listtransactions()\n         [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n {\n     const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n\n     // Make sure the results are valid at least up to the most recent block\n     // the user could have gotten from another RPC command prior to now\n@@ -577,7 +574,6 @@ RPCMethod listsinceblock()\n         [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n {\n     const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n\n     const CWallet& wallet = *pwallet;\n     // Make sure the results are valid at least up to the most recent block\n@@ -719,7 +715,6 @@ RPCMethod gettransaction()\n         [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n {\n     const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n\n     // Make sure the results are valid at least up to the most recent block\n     // the user could have gotten from another RPC command prior to now\n@@ -796,7 +791,6 @@ RPCMethod abandontransaction()\n         [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n {\n     std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n\n     // Make sure the results are valid at least up to the most recent block\n     // the user could have gotten from another RPC command prior to now\n@@ -844,7 +838,6 @@ RPCMethod rescanblockchain()\n         [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n {\n     std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n     CWallet& wallet{*pwallet};\n\n     // Make sure the results are valid at least up to the most recent block\n@@ -932,7 +925,6 @@ RPCMethod abortrescan()\n         [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n {\n     std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n\n     if (!pwallet->IsScanning() || pwallet->IsAbortingRescan()) return false;\n     pwallet->AbortRescan();\ndiff --git a/src/wallet/rpc/util.cpp b/src/wallet/rpc/util.cpp\nindex 77a8745ced..80afdf7107 100644\n--- a/src/wallet/rpc/util.cpp\n+++ b/src/wallet/rpc/util.cpp\n@@ -59,7 +59,7 @@ std::optional<std::string> GetWalletNameFromJSONRPCRequest(const JSONRPCRequest&\n     return std::nullopt;\n }\n\n-std::shared_ptr<CWallet> GetWalletForJSONRPCRequest(const JSONRPCRequest& request)\n+util::NotNullSharedPtr<CWallet> GetWalletForJSONRPCRequest(const JSONRPCRequest& request)\n {\n     CHECK_NONFATAL(request.mode == JSONRPCRequest::EXECUTE);\n     WalletContext& context = EnsureWalletContext(request.context);\ndiff --git a/src/wallet/rpc/util.h b/src/wallet/rpc/util.h\nindex 88fdc6639f..0a77c0d906 100644\n--- a/src/wallet/rpc/util.h\n+++ b/src/wallet/rpc/util.h\n@@ -7,6 +7,7 @@\n\n #include <rpc/util.h>\n #include <script/script.h>\n+#include <util/pointers.h>\n #include <wallet/wallet.h>\n\n #include <any>\n@@ -36,9 +37,9 @@ static const RPCResult RESULT_LAST_PROCESSED_BLOCK { RPCResult::Type::OBJ, \"last\n  * Figures out what wallet, if any, to use for a JSONRPCRequest.\n  *\n  * @param[in] request JSONRPCRequest that wishes to access a wallet\n- * @return nullptr if no wallet should be used, or a pointer to the CWallet\n+ * @return a pointer to the selected CWallet, or throws if no wallet can be selected\n  */\n-std::shared_ptr<CWallet> GetWalletForJSONRPCRequest(const JSONRPCRequest& request);\n+util::NotNullSharedPtr<CWallet> GetWalletForJSONRPCRequest(const JSONRPCRequest& request);\n std::optional<std::string> GetWalletNameFromJSONRPCRequest(const JSONRPCRequest& request);\n /**\n  * Ensures that a wallet name is specified across the endpoint and wallet_name.\ndiff --git a/src/wallet/rpc/wallet.cpp b/src/wallet/rpc/wallet.cpp\nindex 8aa15c7ec5..72beaa6078 100644\n--- a/src/wallet/rpc/wallet.cpp\n+++ b/src/wallet/rpc/wallet.cpp\n@@ -72,7 +72,6 @@ static RPCMethod getwalletinfo()\n         [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n {\n     const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n\n     // Make sure the results are valid at least up to the most recent block\n     // the user could have gotten from another RPC command prior to now\n@@ -304,7 +303,6 @@ static RPCMethod setwalletflag()\n         [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n {\n     std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n\n     std::string flag_str = request.params[0].get_str();\n     bool value = request.params[1].isNull() || request.params[1].get_bool();\n@@ -516,7 +514,6 @@ RPCMethod simulaterawtransaction()\n     [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n {\n     const std::shared_ptr<const CWallet> rpc_wallet = GetWalletForJSONRPCRequest(request);\n-    if (!rpc_wallet) return UniValue::VNULL;\n     const CWallet& wallet = *rpc_wallet;\n\n     LOCK(wallet.cs_wallet);\n@@ -672,7 +669,6 @@ RPCMethod gethdkeys()\n         [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n         {\n             const std::shared_ptr<const CWallet> wallet = GetWalletForJSONRPCRequest(request);\n-            if (!wallet) return UniValue::VNULL;\n\n             LOCK(wallet->cs_wallet);\n\n@@ -770,7 +766,6 @@ static RPCMethod createwalletdescriptor()\n         [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n         {\n             std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);\n-            if (!pwallet) return UniValue::VNULL;\n\n             std::optional<OutputType> output_type = ParseOutputType(request.params[0].get_str());\n             if (!output_type) {\n@@ -860,7 +855,6 @@ RPCMethod addhdkey()\n         [&](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue\n         {\n             std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);\n-            if (!wallet) return UniValue::VNULL;\n\n             if (wallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {\n                 throw JSONRPCError(RPC_WALLET_ERROR, \"addhdkey is not available for wallets without private keys\");\n```"
  },
  {
   "t": "2026-05-23T12:39:08Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/test/CMakeLists.txt",
   "commit": "fa0e12e77bb90bef05bfd8a960e1a28673b575be",
   "in_reply_to": null,
   "text": "[quoted text omitted]\ncurl -fL 'https://raw.githubusercontent.com/microsoft/GSL/688ffcde9018910bef22dae9da9de974803dc982/include/gsl/pointers' -o ./src/util/pointers.h\n\nCould the first commit stay closer to the imported GSL header, with Bitcoin-specific changes applied in follow-up commits?\n\nWe could even cherry-pick it and add the above `curl` reproducer as a scripted diff if you want to push the boundaries of  #35275 :D"
  },
  {
   "t": "2026-05-23T15:17:26Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/util/pointers.h",
   "commit": "fa53bbcb112b5fabf12a0f02622bc890a3bcd5fb",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nSeeing the comments in e.g. https://github.com/bitcoin/bitcoin/issues/24423#issuecomment-1048795957, maybe we could document when this is preferred over other solutions, e.g. that ordinary references remain preferred for simple non-owning access."
  },
  {
   "t": "2026-05-23T15:51:57Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/test/util_pointers_tests.cpp",
   "commit": "fa1e0bb7d0e132b880e850cf8666abc3a5a6e926",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nNit, we could make this explicit to avoid warnings:\n```suggestion\n        ThrowingMoveNullPtr(ThrowingMoveNullPtr&&) noexcept(false) {};\n```"
  },
  {
   "t": "2026-05-23T16:41:16Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/util/pointers.h",
   "commit": "fabc56dc9d1687a4a2a867d2c10a4a8fda4f73ff",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\n`NotNullUniquePtr` can look move-constructible to traits even though actual move construction fails.\nI understand if we keep this, but could we avoid exposing move construction for `util::NotNull`?\nExplicitly deleting move construction/assignment in the public `util::NotNull` wrapper would make the API surface match the intended contract more directly.\n\n```patch\ndiff --git a/src/test/util_pointers_tests.cpp b/src/test/util_pointers_tests.cpp\nindex 85807f8e73..3640e6ccfd 100644\n--- a/src/test/util_pointers_tests.cpp\n+++ b/src/test/util_pointers_tests.cpp\n@@ -14,6 +14,11 @@\n\n static_assert(std::is_same_v<util::NotNullUniquePtr<int>, util::NotNull<std::unique_ptr<int>>>);\n static_assert(std::is_same_v<util::NotNullSharedPtr<int>, util::NotNull<std::shared_ptr<int>>>);\n+static_assert(std::is_copy_constructible_v<util::NotNullSharedPtr<int>>);\n+static_assert(std::is_copy_assignable_v<util::NotNullSharedPtr<int>>);\n+static_assert(!std::is_move_constructible_v<util::NotNullUniquePtr<int>>);\n+static_assert(!std::is_move_constructible_v<util::NotNullSharedPtr<int>>);\n+static_assert(!std::is_move_assignable_v<util::NotNullSharedPtr<int>>);\n\n BOOST_AUTO_TEST_SUITE(util_pointers_tests)\n\ndiff --git a/src/util/pointers.h b/src/util/pointers.h\nindex 73bb45e3e7..948f7021f7 100644\n--- a/src/util/pointers.h\n+++ b/src/util/pointers.h\n@@ -19,6 +19,8 @@\n // * Add NotNullUniquePtr and NotNullSharedPtr aliases to keep smart-pointer\n //   call sites readable.\n // * Add a forwarding constructor for concise non-null smart-pointer returns.\n+// * Delete util::NotNull moves so the wrapper does not advertise misleading\n+//   move operations.\n //\n // All original code is covered by:\n\n@@ -361,6 +363,20 @@ struct NotNull : gsl_detail::strict_not_null<T> {\n     template <typename U>\n     requires (!std::is_pointer_v<T> && std::is_convertible_v<U, T>)\n     constexpr NotNull(U&& u) : Base{std::forward<U>(u)} {}\n+\n+    NotNull(const NotNull&) = default;\n+    NotNull& operator=(const NotNull&) = default;\n+    template <typename U, std::enable_if_t<!std::is_same_v<U, T> && std::is_convertible_v<U, T>, bool> = true>\n+    NotNull& operator=(const NotNull<U>& other)\n+    {\n+        Base::operator=(Base{other});\n+        return *this;\n+    }\n+    // Delete move operations so NotNull does not expose misleading move syntax.\n+    // Store it where the wrapper itself does not need to be moved; use the\n+    // underlying smart pointer at transfer boundaries.\n+    NotNull(NotNull&&) = delete;\n+    NotNull& operator=(NotNull&&) = delete;\n };\n template <typename T>\n NotNull(T) -> NotNull<T>;\n```"
  },
  {
   "t": "2026-05-23T16:47:51Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/dbwrapper.h",
   "commit": "fa0e12e77bb90bef05bfd8a960e1a28673b575be",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nAnother similar one is `BlockTemplateImpl`, it could even simplify passing it as reference:\n```patch\ndiff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp\nindex 16db8692a1..299fa21c8f 100644\n--- a/src/node/interfaces.cpp\n+++ b/src/node/interfaces.cpp\n@@ -58,6 +58,7 @@\n #include <uint256.h>\n #include <univalue.h>\n #include <util/check.h>\n+#include <util/pointers.h>\n #include <util/result.h>\n #include <util/signalinterrupt.h>\n #include <util/string.h>\n@@ -873,7 +874,6 @@ public:\n                                                     m_block_template(std::move(block_template)),\n                                                     m_node(node)\n     {\n-        assert(m_block_template);\n     }\n\n     CBlockHeader getBlockHeader() override\n@@ -914,7 +914,7 @@ public:\n\n     std::unique_ptr<BlockTemplate> waitNext(BlockWaitOptions options) override\n     {\n-        auto new_template = WaitAndCreateNewBlock(chainman(), notifications(), m_node.mempool.get(), m_block_template, options, m_assemble_options, m_interrupt_wait);\n+        auto new_template = WaitAndCreateNewBlock(chainman(), notifications(), m_node.mempool.get(), *m_block_template, options, m_assemble_options, m_interrupt_wait);\n         if (new_template) return std::make_unique<BlockTemplateImpl>(m_assemble_options, std::move(new_template), m_node);\n         return nullptr;\n     }\n@@ -926,7 +926,7 @@ public:\n\n     const BlockAssembler::Options m_assemble_options;\n\n-    const std::unique_ptr<CBlockTemplate> m_block_template;\n+    const util::NotNullUniquePtr<CBlockTemplate> m_block_template;\n\n     bool m_interrupt_wait{false};\n     ChainstateManager& chainman() { return *Assert(m_node.chainman); }\ndiff --git a/src/node/miner.cpp b/src/node/miner.cpp\nindex c9a491ef23..d9cd325a41 100644\n--- a/src/node/miner.cpp\n+++ b/src/node/miner.cpp\n@@ -364,7 +364,7 @@ void InterruptWait(KernelNotifications& kernel_notifications, bool& interrupt_wa\n std::unique_ptr<CBlockTemplate> WaitAndCreateNewBlock(ChainstateManager& chainman,\n                                                       KernelNotifications& kernel_notifications,\n                                                       CTxMemPool* mempool,\n-                                                      const std::unique_ptr<CBlockTemplate>& block_template,\n+                                                      const CBlockTemplate& block_template,\n                                                       const BlockWaitOptions& options,\n                                                       const BlockAssembler::Options& assemble_options,\n                                                       bool& interrupt_wait)\n@@ -391,7 +391,7 @@ std::unique_ptr<CBlockTemplate> WaitAndCreateNewBlock(ChainstateManager& chainma\n                 // We assume tip_block is set, because this is an instance\n                 // method on BlockTemplate and no template could have been\n                 // generated before a tip exists.\n-                tip_changed = Assume(tip_block) && tip_block != block_template->block.hashPrevBlock;\n+                tip_changed = Assume(tip_block) && tip_block != block_template.block.hashPrevBlock;\n                 return tip_changed || chainman.m_interrupt || interrupt_wait;\n             });\n             if (interrupt_wait) {\n@@ -435,7 +435,7 @@ std::unique_ptr<CBlockTemplate> WaitAndCreateNewBlock(ChainstateManager& chainma\n\n             // Calculate the original template total fees if we haven't already\n             if (current_fees == -1) {\n-                current_fees = std::accumulate(block_template->vTxFees.begin(), block_template->vTxFees.end(), CAmount{0});\n+                current_fees = std::accumulate(block_template.vTxFees.begin(), block_template.vTxFees.end(), CAmount{0});\n             }\n\n             // Check if fees increased enough to return the new template\ndiff --git a/src/node/miner.h b/src/node/miner.h\nindex 5c8668771f..1a0e3f8d3d 100644\n--- a/src/node/miner.h\n+++ b/src/node/miner.h\n@@ -150,7 +150,7 @@ void InterruptWait(KernelNotifications& kernel_notifications, bool& interrupt_wa\n std::unique_ptr<CBlockTemplate> WaitAndCreateNewBlock(ChainstateManager& chainman,\n                                                       KernelNotifications& kernel_notifications,\n                                                       CTxMemPool* mempool,\n-                                                      const std::unique_ptr<CBlockTemplate>& block_template,\n+                                                      const CBlockTemplate& block_template,\n                                                       const BlockWaitOptions& options,\n                                                       const BlockAssembler::Options& assemble_options,\n                                                       bool& interrupt_wait);\n```"
  },
  {
   "t": "2026-05-23T16:52:24Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/dbwrapper.h",
   "commit": "fa0e12e77bb90bef05bfd8a960e1a28673b575be",
   "in_reply_to": 3293173669,
   "text": "[quoted text omitted]\n\nAnd a few other ones that we're already treating as non-null implicitly: their constructors initialize `m_impl` with `std::make_unique`, and the members are const, so the pointer cannot later be reset or reassigned:\n```patch\ndiff --git a/src/addrman.h b/src/addrman.h\nindex 94e7d3e653..7a1888d38e 100644\n--- a/src/addrman.h\n+++ b/src/addrman.h\n@@ -10,6 +10,7 @@\n #include <netgroup.h>\n #include <protocol.h>\n #include <streams.h>\n+#include <util/pointers.h>\n #include <util/time.h>\n\n #include <cstdint>\n@@ -109,7 +110,7 @@ struct AddressPosition {\n class AddrMan\n {\n protected:\n-    const std::unique_ptr<AddrManImpl> m_impl;\n+    const util::NotNullUniquePtr<AddrManImpl> m_impl;\n\n public:\n     explicit AddrMan(const NetGroupManager& netgroupman, bool deterministic, int32_t consistency_check_ratio);\ndiff --git a/src/node/txreconciliation.h b/src/node/txreconciliation.h\nindex 68deeabaf6..97bf1bfd44 100644\n--- a/src/node/txreconciliation.h\n+++ b/src/node/txreconciliation.h\n@@ -7,6 +7,7 @@\n\n #include <net.h>\n #include <sync.h>\n+#include <util/pointers.h>\n\n #include <memory>\n #include <tuple>\n@@ -52,7 +53,7 @@ class TxReconciliationTracker\n {\n private:\n     class Impl;\n-    const std::unique_ptr<Impl> m_impl;\n+    const util::NotNullUniquePtr<Impl> m_impl;\n\n public:\n     explicit TxReconciliationTracker(uint32_t recon_version);\ndiff --git a/src/txrequest.h b/src/txrequest.h\nindex 93972a88c2..6fd92c0ec8 100644\n--- a/src/txrequest.h\n+++ b/src/txrequest.h\n@@ -8,6 +8,7 @@\n #include <primitives/transaction.h>\n #include <net.h>\n #include <uint256.h>\n+#include <util/pointers.h>\n\n #include <chrono>\n #include <cstdint>\n@@ -100,7 +101,7 @@\n class TxRequestTracker {\n     // Avoid littering this header file with implementation details.\n     class Impl;\n-    const std::unique_ptr<Impl> m_impl;\n+    const util::NotNullUniquePtr<Impl> m_impl;\n\n public:\n     //! Construct a TxRequestTracker.\n```"
  },
  {
   "t": "2026-05-23T17:18:33Z",
   "kind": "review",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "state": "CHANGES_REQUESTED",
   "commit": "77773e8c856fff45ad432edfe120eb13a6be95a6",
   "text": "Concept ACK, I think this could help in a few cases where the pointer-like type is part of the contract and a plain reference would not preserve the ownership/storage semantics.\nThis also feels aligned with ongoing efforts like https://github.com/bitcoin/bitcoin/pull/35229 (cc: @optout21).\n\nThe part that bothers me most is that `util::NotNull<std::unique_ptr<T>>` does not quite feel like a first-class type yet. The construction and call sites are fairly verbose, which makes the stronger invariant harder to adopt/read. I left a few suggestions around making common smart-pointer usage more compact, adding a few assertions/tests so the API is less surprising, and pointing out a few other places where this pattern may fit."
  },
  {
   "t": "2026-05-26T11:08:40Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/test/CMakeLists.txt",
   "commit": "fa0e12e77bb90bef05bfd8a960e1a28673b575be",
   "in_reply_to": 3292780737,
   "text": "[quoted text omitted]\n\nthx, done"
  },
  {
   "t": "2026-05-26T11:42:30Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/test/util_pointers_tests.cpp",
   "commit": "fa1e0bb7d0e132b880e850cf8666abc3a5a6e926",
   "in_reply_to": 3292594365,
   "text": "Sure, but this is just how it works on master. E.g. in a release build with an injected bug:\n\n-> `getchaintxstats`\n<-\n```\nInternal bug detected: pindex != nullptr\nrpc/blockchain.cpp:1876\n...\n```\n\nHappy to review a pull request changing/improving that (e.g. by appending `\"failed\"`), but it seems unrelated to the changes here."
  },
  {
   "t": "2026-05-26T16:55:53Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/test/util_pointers_tests.cpp",
   "commit": "fa1e0bb7d0e132b880e850cf8666abc3a5a6e926",
   "in_reply_to": 3293063262,
   "text": "Turned into compile-time test instead."
  },
  {
   "t": "2026-05-26T16:57:05Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/util/pointers.h",
   "commit": "fabc56dc9d1687a4a2a867d2c10a4a8fda4f73ff",
   "in_reply_to": 3292638109,
   "text": "Is this still relevant after the last push?"
  },
  {
   "t": "2026-05-26T17:02:45Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/util/pointers.h",
   "commit": "fabc56dc9d1687a4a2a867d2c10a4a8fda4f73ff",
   "in_reply_to": 3293165660,
   "text": "Heh, good point, but I think I'd like to go the other way and make it moveable. I have a commit for this, but removed it, see https://github.com/bitcoin/bitcoin/pull/34844#issuecomment-4290176661. Move semantics can be done in a later commit/pull request.\n\nedit: If we wait long enough, C++29 or later may ship with trivial reloc (or similar), which makes this safer to implement from a static analysis perspective :sweat_smile:"
  },
  {
   "t": "2026-05-26T17:10:26Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/util/pointers.h",
   "commit": "fa53bbcb112b5fabf12a0f02622bc890a3bcd5fb",
   "in_reply_to": 3293014021,
   "text": "thx, added a doxygen comment here."
  },
  {
   "t": "2026-05-26T17:37:09Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/dbwrapper.h",
   "commit": "fa0e12e77bb90bef05bfd8a960e1a28673b575be",
   "in_reply_to": 3293173669,
   "text": "[quoted text omitted]\n\nAh, right. Though, my preference would be to make all `NotNull` constructor calls explicit, as they serve as a drop-in replacement for `Assert`. Also, one could consider making `NotNull` movable and then construct it at the call site here.\n\n[quoted text omitted]\nNice. Happy to push such a commit. Though, if we use it for m_impl, it should be used in all places in the codebase. E.g. the following is missing from your diff:\n\n* `src/musig.h:    std::unique_ptr<MuSig2SecNonceImpl> m_impl;`\n* `const std::unique_ptr<TxDownloadManagerImpl> m_impl;`\n\nIf you send a full diff, I can include it here ( Let me know if you want to be listed as co-author in any commits). Or you can push a branch to your liking to your repo, and I can take it as-is."
  },
  {
   "t": "2026-05-26T18:19:43Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/net.cpp",
   "commit": "77773e8c856fff45ad432edfe120eb13a6be95a6",
   "in_reply_to": 3292698202,
   "text": "[quoted text omitted]\nThx, but I think the diff is not quite right:\n\n* I think not-null ctors should be explicit, where possible, so the function should `return util::NotNull{pwallet};`\n* I think we want to ideally keep the not-null type for as long as possible. So decaying to a nullable shared_ptr and removing the null checks seems inconsistent.\n\nThe correct diff would be:\n\na diff\n\n```diff\ndiff --git a/src/wallet/rpc/addresses.cpp b/src/wallet/rpc/addresses.cpp\nindex ed966d8944..a13f2baaeb 100644\n--- a/src/wallet/rpc/addresses.cpp\n+++ b/src/wallet/rpc/addresses.cpp\n@@ -40,4 +40,3 @@ RPCMethod getnewaddress()\n {\n-    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n+    const util::NotNullSharedPtr<CWallet> pwallet{GetWalletForJSONRPCRequest(request)};\n\n@@ -89,4 +88,3 @@ RPCMethod getrawchangeaddress()\n {\n-    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n+    const util::NotNullSharedPtr<CWallet> pwallet{GetWalletForJSONRPCRequest(request)};\n\n@@ -133,4 +131,3 @@ RPCMethod setlabel()\n {\n-    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n+    const util::NotNullSharedPtr<CWallet> pwallet{GetWalletForJSONRPCRequest(request)};\n\n@@ -184,4 +181,3 @@ RPCMethod listaddressgroupings()\n {\n-    const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n+    const util::NotNullSharedPtr<const CWallet> pwallet{GetWalletForJSONRPCRequest(request)};\n\n@@ -233,4 +229,3 @@ RPCMethod keypoolrefill()\n {\n-    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n+    const util::NotNullSharedPtr<CWallet> pwallet{GetWalletForJSONRPCRequest(request)};\n\n@@ -424,4 +419,3 @@ RPCMethod getaddressinfo()\n {\n-    const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n+    const util::NotNullSharedPtr<const CWallet> pwallet{GetWalletForJSONRPCRequest(request)};\n\n@@ -537,4 +531,3 @@ RPCMethod getaddressesbylabel()\n {\n-    const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n+    const util::NotNullSharedPtr<const CWallet> pwallet{GetWalletForJSONRPCRequest(request)};\n\n@@ -601,4 +594,3 @@ RPCMethod listlabels()\n {\n-    const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n+    const util::NotNullSharedPtr<const CWallet> pwallet{GetWalletForJSONRPCRequest(request)};\n\n@@ -649,5 +641,3 @@ RPCMethod walletdisplayaddress()\n         {\n-            std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);\n-            if (!wallet) return UniValue::VNULL;\n-            CWallet* const pwallet = wallet.get();\n+            const util::NotNullSharedPtr<CWallet> pwallet{GetWalletForJSONRPCRequest(request)};\n\ndiff --git a/src/wallet/rpc/backup.cpp b/src/wallet/rpc/backup.cpp\nindex 396be62825..7113799f1c 100644\n--- a/src/wallet/rpc/backup.cpp\n+++ b/src/wallet/rpc/backup.cpp\n@@ -51,4 +51,3 @@ RPCMethod importprunedfunds()\n {\n-    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n+    const util::NotNullSharedPtr<CWallet> pwallet{GetWalletForJSONRPCRequest(request)};\n\n@@ -109,4 +108,3 @@ RPCMethod removeprunedfunds()\n {\n-    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n+    const util::NotNullSharedPtr<CWallet> pwallet{GetWalletForJSONRPCRequest(request)};\n\n@@ -378,4 +376,3 @@ RPCMethod importdescriptors()\n {\n-    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(main_request);\n-    if (!pwallet) return UniValue::VNULL;\n+    const util::NotNullSharedPtr<CWallet> pwallet{GetWalletForJSONRPCRequest(main_request)};\n     CWallet& wallet{*pwallet};\n@@ -516,4 +513,3 @@ RPCMethod listdescriptors()\n {\n-    const std::shared_ptr<const CWallet> wallet = GetWalletForJSONRPCRequest(request);\n-    if (!wallet) return UniValue::VNULL;\n+    const util::NotNullSharedPtr<const CWallet> wallet{GetWalletForJSONRPCRequest(request)};\n\n@@ -609,4 +605,3 @@ RPCMethod backupwallet()\n {\n-    const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n+    const util::NotNullSharedPtr<const CWallet> pwallet{GetWalletForJSONRPCRequest(request)};\n\ndiff --git a/src/wallet/rpc/coins.cpp b/src/wallet/rpc/coins.cpp\nindex ab869b0d3f..0e954e9146 100644\n--- a/src/wallet/rpc/coins.cpp\n+++ b/src/wallet/rpc/coins.cpp\n@@ -106,4 +106,3 @@ RPCMethod getreceivedbyaddress()\n {\n-    const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n+    const util::NotNullSharedPtr<const CWallet> pwallet{GetWalletForJSONRPCRequest(request)};\n\n@@ -148,4 +147,3 @@ RPCMethod getreceivedbylabel()\n {\n-    const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n+    const util::NotNullSharedPtr<const CWallet> pwallet{GetWalletForJSONRPCRequest(request)};\n\n@@ -189,4 +187,3 @@ RPCMethod getbalance()\n {\n-    const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n+    const util::NotNullSharedPtr<const CWallet> pwallet{GetWalletForJSONRPCRequest(request)};\n\n@@ -259,4 +256,3 @@ RPCMethod lockunspent()\n {\n-    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n+    const util::NotNullSharedPtr<CWallet> pwallet{GetWalletForJSONRPCRequest(request)};\n\n@@ -377,4 +373,3 @@ RPCMethod listlockunspent()\n {\n-    const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n+    const util::NotNullSharedPtr<const CWallet> pwallet{GetWalletForJSONRPCRequest(request)};\n\n@@ -425,4 +420,3 @@ RPCMethod getbalances()\n {\n-    const std::shared_ptr<const CWallet> rpc_wallet = GetWalletForJSONRPCRequest(request);\n-    if (!rpc_wallet) return UniValue::VNULL;\n+    const util::NotNullSharedPtr<const CWallet> rpc_wallet{GetWalletForJSONRPCRequest(request)};\n     const CWallet& wallet = *rpc_wallet;\n@@ -521,4 +515,3 @@ RPCMethod listunspent()\n {\n-    const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n+    const util::NotNullSharedPtr<const CWallet> pwallet{GetWalletForJSONRPCRequest(request)};\n\ndiff --git a/src/wallet/rpc/encrypt.cpp b/src/wallet/rpc/encrypt.cpp\nindex 68a80eb80e..85bd0b4791 100644\n--- a/src/wallet/rpc/encrypt.cpp\n+++ b/src/wallet/rpc/encrypt.cpp\n@@ -36,5 +36,3 @@ RPCMethod walletpassphrase()\n {\n-    std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);\n-    if (!wallet) return UniValue::VNULL;\n-    CWallet* const pwallet = wallet.get();\n+    const util::NotNullSharedPtr<CWallet> pwallet{GetWalletForJSONRPCRequest(request)};\n\n@@ -100,3 +98,3 @@ RPCMethod walletpassphrase()\n     // is acquired in the callback then the wallet is still loaded.\n-    std::weak_ptr<CWallet> weak_wallet = wallet;\n+    std::weak_ptr<CWallet> weak_wallet{pwallet.get()};\n     context.scheduler->scheduleFromNow([weak_wallet, relock_time] {\n@@ -133,4 +131,3 @@ RPCMethod walletpassphrasechange()\n {\n-    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n+    const util::NotNullSharedPtr<CWallet> pwallet{GetWalletForJSONRPCRequest(request)};\n\n@@ -198,4 +195,3 @@ RPCMethod walletlock()\n {\n-    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n+    const util::NotNullSharedPtr<CWallet> pwallet{GetWalletForJSONRPCRequest(request)};\n\n@@ -251,4 +247,3 @@ RPCMethod encryptwallet()\n {\n-    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n+    const util::NotNullSharedPtr<CWallet> pwallet{GetWalletForJSONRPCRequest(request)};\n\ndiff --git a/src/wallet/rpc/signmessage.cpp b/src/wallet/rpc/signmessage.cpp\nindex bd49f3e393..df7cee1a28 100644\n--- a/src/wallet/rpc/signmessage.cpp\n+++ b/src/wallet/rpc/signmessage.cpp\n@@ -38,4 +38,3 @@ RPCMethod signmessage()\n         {\n-            const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);\n-            if (!pwallet) return UniValue::VNULL;\n+            const util::NotNullSharedPtr<const CWallet> pwallet{GetWalletForJSONRPCRequest(request)};\n\ndiff --git a/src/wallet/rpc/spend.cpp b/src/wallet/rpc/spend.cpp\nindex b6cdc8600f..f1623fa662 100644\n--- a/src/wallet/rpc/spend.cpp\n+++ b/src/wallet/rpc/spend.cpp\n@@ -289,4 +289,3 @@ RPCMethod sendtoaddress()\n {\n-    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n+    const util::NotNullSharedPtr<CWallet> pwallet{GetWalletForJSONRPCRequest(request)};\n\n@@ -393,4 +392,3 @@ RPCMethod sendmany()\n {\n-    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n+    const util::NotNullSharedPtr<CWallet> pwallet{GetWalletForJSONRPCRequest(request)};\n\n@@ -800,4 +798,3 @@ RPCMethod fundrawtransaction()\n {\n-    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n+    const util::NotNullSharedPtr<CWallet> pwallet{GetWalletForJSONRPCRequest(request)};\n\n@@ -901,4 +898,3 @@ RPCMethod signrawtransactionwithwallet()\n {\n-    const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n+    const util::NotNullSharedPtr<const CWallet> pwallet{GetWalletForJSONRPCRequest(request)};\n\n@@ -1034,4 +1030,3 @@ static RPCMethod bumpfee_helper(std::string method_name)\n {\n-    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n+    const util::NotNullSharedPtr<CWallet> pwallet{GetWalletForJSONRPCRequest(request)};\n\n@@ -1264,4 +1259,3 @@ RPCMethod send()\n         {\n-            std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);\n-            if (!pwallet) return UniValue::VNULL;\n+            const util::NotNullSharedPtr<CWallet> pwallet{GetWalletForJSONRPCRequest(request)};\n\n@@ -1379,3 +1373,2 @@ RPCMethod sendall()\n             std::shared_ptr<CWallet> const pwallet{GetWalletForJSONRPCRequest(request)};\n-            if (!pwallet) return UniValue::VNULL;\n             // Make sure the results are valid at least up to the most recent block\n@@ -1624,4 +1617,3 @@ RPCMethod walletprocesspsbt()\n {\n-    const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n+    const util::NotNullSharedPtr<const CWallet> pwallet{GetWalletForJSONRPCRequest(request)};\n\n@@ -1756,4 +1748,3 @@ RPCMethod walletcreatefundedpsbt()\n {\n-    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n+    const util::NotNullSharedPtr<CWallet> pwallet{GetWalletForJSONRPCRequest(request)};\n\ndiff --git a/src/wallet/rpc/transactions.cpp b/src/wallet/rpc/transactions.cpp\nindex f69082e1e9..8d0d65aea1 100644\n--- a/src/wallet/rpc/transactions.cpp\n+++ b/src/wallet/rpc/transactions.cpp\n@@ -228,4 +228,3 @@ RPCMethod listreceivedbyaddress()\n {\n-    const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n+    const util::NotNullSharedPtr<const CWallet> pwallet{GetWalletForJSONRPCRequest(request)};\n\n@@ -273,4 +272,3 @@ RPCMethod listreceivedbylabel()\n {\n-    const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n+    const util::NotNullSharedPtr<const CWallet> pwallet{GetWalletForJSONRPCRequest(request)};\n\n@@ -469,4 +467,3 @@ RPCMethod listtransactions()\n {\n-    const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n+    const util::NotNullSharedPtr<const CWallet> pwallet{GetWalletForJSONRPCRequest(request)};\n\n@@ -580,4 +577,3 @@ RPCMethod listsinceblock()\n {\n-    const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n+    const util::NotNullSharedPtr<const CWallet> pwallet{GetWalletForJSONRPCRequest(request)};\n\n@@ -722,4 +718,3 @@ RPCMethod gettransaction()\n {\n-    const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n+    const util::NotNullSharedPtr<const CWallet> pwallet{GetWalletForJSONRPCRequest(request)};\n\n@@ -799,4 +794,3 @@ RPCMethod abandontransaction()\n {\n-    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n+    const util::NotNullSharedPtr<CWallet> pwallet{GetWalletForJSONRPCRequest(request)};\n\n@@ -847,4 +841,3 @@ RPCMethod rescanblockchain()\n {\n-    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n+    const util::NotNullSharedPtr<CWallet> pwallet{GetWalletForJSONRPCRequest(request)};\n     CWallet& wallet{*pwallet};\n@@ -935,4 +928,3 @@ RPCMethod abortrescan()\n {\n-    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n+    const util::NotNullSharedPtr<CWallet> pwallet{GetWalletForJSONRPCRequest(request)};\n\ndiff --git a/src/wallet/rpc/util.cpp b/src/wallet/rpc/util.cpp\nindex 77a8745ced..9d29661eca 100644\n--- a/src/wallet/rpc/util.cpp\n+++ b/src/wallet/rpc/util.cpp\n@@ -61,3 +61,3 @@ std::optional<std::string> GetWalletNameFromJSONRPCRequest(const JSONRPCRequest&\n\n-std::shared_ptr<CWallet> GetWalletForJSONRPCRequest(const JSONRPCRequest& request)\n+util::NotNullSharedPtr<CWallet> GetWalletForJSONRPCRequest(const JSONRPCRequest& request)\n {\n@@ -69,3 +69,3 @@ std::shared_ptr<CWallet> GetWalletForJSONRPCRequest(const JSONRPCRequest& reques\n         if (!pwallet) throw JSONRPCError(RPC_WALLET_NOT_FOUND, \"Requested wallet does not exist or is not loaded\");\n-        return pwallet;\n+        return util::NotNull{pwallet};\n     }\n@@ -74,3 +74,3 @@ std::shared_ptr<CWallet> GetWalletForJSONRPCRequest(const JSONRPCRequest& reques\n     auto wallet = GetDefaultWallet(context, count);\n-    if (wallet) return wallet;\n+    if (wallet) return util::NotNull{wallet};\n\ndiff --git a/src/wallet/rpc/util.h b/src/wallet/rpc/util.h\nindex 88fdc6639f..0a77c0d906 100644\n--- a/src/wallet/rpc/util.h\n+++ b/src/wallet/rpc/util.h\n@@ -9,2 +9,3 @@\n #include <script/script.h>\n+#include <util/pointers.h>\n #include <wallet/wallet.h>\n@@ -38,5 +39,5 @@ static const RPCResult RESULT_LAST_PROCESSED_BLOCK { RPCResult::Type::OBJ, \"last\n  * @param[in] request JSONRPCRequest that wishes to access a wallet\n- * @return nullptr if no wallet should be used, or a pointer to the CWallet\n+ * @return a pointer to the selected CWallet, or throws if no wallet can be selected\n  */\n-std::shared_ptr<CWallet> GetWalletForJSONRPCRequest(const JSONRPCRequest& request);\n+util::NotNullSharedPtr<CWallet> GetWalletForJSONRPCRequest(const JSONRPCRequest& request);\n std::optional<std::string> GetWalletNameFromJSONRPCRequest(const JSONRPCRequest& request);\ndiff --git a/src/wallet/rpc/wallet.cpp b/src/wallet/rpc/wallet.cpp\nindex 8aa15c7ec5..c3569ec118 100644\n--- a/src/wallet/rpc/wallet.cpp\n+++ b/src/wallet/rpc/wallet.cpp\n@@ -73,4 +73,3 @@ static RPCMethod getwalletinfo()\n {\n-    const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n+    const util::NotNullSharedPtr<const CWallet> pwallet{GetWalletForJSONRPCRequest(request)};\n\n@@ -305,4 +304,3 @@ static RPCMethod setwalletflag()\n {\n-    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);\n-    if (!pwallet) return UniValue::VNULL;\n+    const util::NotNullSharedPtr<CWallet> pwallet{GetWalletForJSONRPCRequest(request)};\n\n@@ -517,4 +515,3 @@ RPCMethod simulaterawtransaction()\n {\n-    const std::shared_ptr<const CWallet> rpc_wallet = GetWalletForJSONRPCRequest(request);\n-    if (!rpc_wallet) return UniValue::VNULL;\n+    const util::NotNullSharedPtr<const CWallet> rpc_wallet{GetWalletForJSONRPCRequest(request)};\n     const CWallet& wallet = *rpc_wallet;\n@@ -673,4 +670,3 @@ RPCMethod gethdkeys()\n         {\n-            const std::shared_ptr<const CWallet> wallet = GetWalletForJSONRPCRequest(request);\n-            if (!wallet) return UniValue::VNULL;\n+            const util::NotNullSharedPtr<const CWallet> wallet{GetWalletForJSONRPCRequest(request)};\n\n@@ -771,4 +767,3 @@ static RPCMethod createwalletdescriptor()\n         {\n-            std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);\n-            if (!pwallet) return UniValue::VNULL;\n+            const util::NotNullSharedPtr<CWallet> pwallet{GetWalletForJSONRPCRequest(request)};\n\n@@ -861,4 +856,3 @@ RPCMethod addhdkey()\n         {\n-            std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);\n-            if (!wallet) return UniValue::VNULL;\n+            const util::NotNullSharedPtr<CWallet> wallet{GetWalletForJSONRPCRequest(request)};\n ```\n\nHowever, this raises the concern that thread-safety annotations will be broken by this.\n\nI guess this makes sense, as clang can not figure out that the `operator->` returns the same smart wallet pointer every time it is called. So I guess my above diff is still incomplete and would likely require a change to first assign a wallet reference:\n\n```cpp\nconst util::NotNullSharedPtr<const CWallet> pwallet{GetWalletForJSONRPCRequest(request)};\nconst CWallet& wallet{*pwallet};\nLOCK(wallet.cs_wallet);\nwallet.DoThing();\n..."
  },
  {
   "t": "2026-05-26T18:21:39Z",
   "kind": "force_push",
   "who": "maflcko",
   "commit": "fae0a346c1ea2c96160448f660b37749d695265e"
  },
  {
   "t": "2026-05-26T18:23:09Z",
   "kind": "review",
   "who": "maflcko",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "77773e8c856fff45ad432edfe120eb13a6be95a6",
   "text": "Thx for the review. I pushed some changes and replied to all comments."
  },
  {
   "t": "2026-05-26T18:25:40Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/dbwrapper.h",
   "commit": "faef857079bd2467e2d04cbcd814b0b36e9fb7b0",
   "in_reply_to": 3292627623,
   "text": "thx, done"
  },
  {
   "t": "2026-06-02T10:27:53Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/util/pointers.h",
   "commit": "fabc56dc9d1687a4a2a867d2c10a4a8fda4f73ff",
   "in_reply_to": 3292638109,
   "text": "Can be resolved, thanks"
  },
  {
   "t": "2026-06-02T10:33:24Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/util/pointers.h",
   "commit": "fabc56dc9d1687a4a2a867d2c10a4a8fda4f73ff",
   "in_reply_to": 3293165660,
   "text": "Could we maybe document that it's not movable yet and remove it when it becomes movable? It's not intuitive what should happen in case of a move."
  },
  {
   "t": "2026-06-02T10:38:48Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/dbwrapper.h",
   "commit": "fa0e12e77bb90bef05bfd8a960e1a28673b575be",
   "in_reply_to": 3293173669,
   "text": "[quoted text omitted]\n\nSeems simple enough to do here or in a follow-up, it's not a blocker from me.\n\n[quoted text omitted]\nI usually add coauthors when their comments triggered a change that makes the PR better."
  },
  {
   "t": "2026-06-02T10:39:26Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/test/util_pointers_tests.cpp",
   "commit": "fa1e0bb7d0e132b880e850cf8666abc3a5a6e926",
   "in_reply_to": 3292594365,
   "text": "[quoted text omitted]\n\nI know, it's why I wrote:\n[quoted text omitted]"
  },
  {
   "t": "2026-06-02T10:43:54Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/net.cpp",
   "commit": "77773e8c856fff45ad432edfe120eb13a6be95a6",
   "in_reply_to": 3292698202,
   "text": "[quoted text omitted]\n\nThis still seems like something we could still assert for safety, i.e. that raw pointers do not implicitly convert to `util::NotNull`.\n\nThe verbosity still bothers me, but every other workaround I found was too complicated - we can simplify in a followup, if needed."
  },
  {
   "t": "2026-06-02T10:55:53Z",
   "kind": "review",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "state": "APPROVED",
   "commit": "fae0a346c1ea2c96160448f660b37749d695265e",
   "text": "ACK fae0a346c1ea2c96160448f660b37749d695265e\n\nRemaining nits aren't critical, we can do them in follow-ups."
  },
  {
   "t": "2026-06-04T11:27:25Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/util_pointers_tests.cpp",
   "commit": "fa1e0bb7d0e132b880e850cf8666abc3a5a6e926",
   "in_reply_to": 3292594365,
   "text": "Proposed improvement for this: #35461"
  },
  {
   "t": "2026-06-16T09:31:51Z",
   "kind": "force_push",
   "who": "maflcko",
   "commit": "fabc56dc9d1687a4a2a867d2c10a4a8fda4f73ff"
  },
  {
   "t": "2026-06-16T09:39:58Z",
   "kind": "force_push",
   "who": "maflcko",
   "commit": "fa7cd208fa15906dd6d1eee84a588227231dc912"
  },
  {
   "t": "2026-06-16T10:00:42Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/util/pointers.h",
   "commit": "fabc56dc9d1687a4a2a867d2c10a4a8fda4f73ff",
   "in_reply_to": 3293165660,
   "text": "I pushed a doc-only update for now, but let me write a longer reply here in the future ..."
  },
  {
   "t": "2026-06-16T10:14:44Z",
   "kind": "force_push",
   "who": "maflcko",
   "commit": "6389aa8f15aa4ed55d76afda36c0b1c870f1c9d0"
  },
  {
   "t": "2026-06-16T10:27:05Z",
   "kind": "comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "text": "untested diff review ACK 6389aa8f15aa4ed55d76afda36c0b1c870f1c9d0reACK\n\nMostly whitespace and formatting and comment changes since last review.\n\nChanges since my last ack\n\n```patch\ndiff --git a/src/util/pointers.h b/src/util/pointers.h\nindex aff946a558..e7dca4b2ba 100644\n--- a/src/util/pointers.h\n+++ b/src/util/pointers.h\n@@ -3,7 +3,7 @@\n // file COPYING or https://opensource.org/license/mit/.\n\n // This file is based on\n-// https://github.com/microsoft/GSL/blob/756c91ab895aa52f650599bb1a3fc131f1f4b5ef/include/gsl/pointers,\n+// https://github.com/microsoft/GSL/blob/main/include/gsl/pointers,\n // with some modifications:\n // * Remove everything around GSL_DEPRECATED and GSL_NO_IOSTREAMS, because it\n //   is not needed.\n@@ -16,8 +16,7 @@\n //    - strict_make_not_null, because it is not needed.\n // * Remove the not_null->strict_not_null converting constructors, because they\n //   are not needed.\n-// * Add NotNullUniquePtr and NotNullSharedPtr aliases to keep smart-pointer\n-//   call sites readable.\n+// * Add util namespace for aliases to be used by Bitcoin Core code.\n //\n // All original code is covered by:\n\n@@ -42,11 +41,11 @@\n\n #include <util/check.h>\n\n-#include <cstddef>\n-#include <functional>\n-#include <memory>\n-#include <type_traits>\n-#include <utility>\n+#include <cstddef>     // for ptrdiff_t, nullptr_t, size_t\n+#include <functional>  // for less, greater\n+#include <memory>      // for shared_ptr, unique_ptr, hash\n+#include <type_traits> // for enable_if_t, is_convertible, is_assignable\n+#include <utility>     // for declval, forward\n\n namespace gsl_detail\n {\n@@ -66,24 +65,26 @@ namespace details\n     {\n     };\n\n-    // Resolves to the more efficient of `const T` or `const T&`, in the context of returning a const-qualified value\n-    // of type T.\n+    // Resolves to the more efficient of `const T` or `const T&`, in the context of returning a\n+    // const-qualified value of type T.\n     //\n-    // Copied from cppfront's implementation of the CppCoreGuidelines F.16 (https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#rf-in)\n-    template<typename T>\n-    using value_or_reference_return_t = std::conditional_t<\n-                                            sizeof(T) <= 2*sizeof(void*) && std::is_trivially_copy_constructible<T>::value,\n-                                            const T,\n-                                            const T&>;\n+    // Copied from cppfront's implementation of the CppCoreGuidelines F.16\n+    // (https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#rf-in)\n+    template <typename T>\n+    using value_or_reference_return_t =\n+        std::conditional_t<sizeof(T) <= 2 * sizeof(void*) &&\n+                               std::is_trivially_copy_constructible<T>::value,\n+                           const T, const T&>;\n\n } // namespace details\n\n //\n // owner\n //\n-// `gsl::owner<T>` is designed as a safety mechanism for code that must deal directly with raw pointers that own memory.\n-// Ideally such code should be restricted to the implementation of low-level abstractions. `gsl::owner` can also be used\n-// as a stepping point in converting legacy code to use more modern RAII constructs, such as smart pointers.\n+// `gsl::owner<T>` is designed as a safety mechanism for code that must deal directly with raw\n+// pointers that own memory. Ideally such code should be restricted to the implementation of\n+// low-level abstractions. `gsl::owner` can also be used as a stepping point in converting legacy\n+// code to use more modern RAII constructs, such as smart pointers.\n //\n // T must be a pointer type\n // - disallow construction from any type other than pointer type\n@@ -114,19 +115,23 @@ public:\n     using element_type = T;\n\n     template <typename U, typename = std::enable_if_t<std::is_convertible<U, T>::value>>\n-    constexpr not_null(U&& u) noexcept(std::is_nothrow_move_constructible<T>::value) : ptr_(std::forward<U>(u))\n+    constexpr not_null(U&& u) noexcept(std::is_nothrow_move_constructible<T>::value)\n+        : ptr_(std::forward<U>(u))\n     {\n         Assert(ptr_ != nullptr);\n     }\n\n     template <typename = std::enable_if_t<!std::is_same<std::nullptr_t, T>::value>>\n-    constexpr not_null(T u) noexcept(std::is_nothrow_move_constructible<T>::value) : ptr_(std::move(u))\n+    constexpr not_null(T u) noexcept(std::is_nothrow_move_constructible<T>::value)\n+        : ptr_(std::move(u))\n     {\n         Assert(ptr_ != nullptr);\n     }\n\n     template <typename U, typename = std::enable_if_t<std::is_convertible<U, T>::value>>\n-    constexpr not_null(const not_null<U>& other) noexcept(std::is_nothrow_move_constructible<T>::value) : not_null(other.get())\n+    constexpr not_null(const not_null<U>& other) noexcept(\n+        std::is_nothrow_move_constructible<T>::value)\n+        : not_null(other.get())\n     {}\n\n     not_null(const not_null& other) = default;\n@@ -160,7 +165,9 @@ private:\n     T ptr_;\n };\n\n-template <typename T, std::enable_if_t<std::is_move_assignable<T>::value && std::is_move_constructible<T>::value, bool> = true>\n+template <typename T, std::enable_if_t<std::is_move_assignable<T>::value &&\n+                                           std::is_move_constructible<T>::value,\n+                                       bool> = true>\n void swap(not_null<T>& a, not_null<T>& b) noexcept\n {\n     a.swap(b);\n@@ -174,7 +181,7 @@ auto make_not_null(T&& t) noexcept\n\n template <class T, class U>\n constexpr auto operator==(const not_null<T>& lhs,\n-                const not_null<U>& rhs) noexcept(noexcept(lhs.get() == rhs.get()))\n+                          const not_null<U>& rhs) noexcept(noexcept(lhs.get() == rhs.get()))\n     -> decltype(lhs.get() == rhs.get())\n {\n     return lhs.get() == rhs.get();\n@@ -182,39 +189,41 @@ constexpr auto operator==(const not_null<T>& lhs,\n\n template <class T, class U>\n constexpr auto operator!=(const not_null<T>& lhs,\n-                const not_null<U>& rhs) noexcept(noexcept(lhs.get() != rhs.get()))\n+                          const not_null<U>& rhs) noexcept(noexcept(lhs.get() != rhs.get()))\n     -> decltype(lhs.get() != rhs.get())\n {\n     return lhs.get() != rhs.get();\n }\n\n template <class T, class U>\n-constexpr auto operator<(const not_null<T>& lhs,\n-               const not_null<U>& rhs) noexcept(noexcept(std::less<>{}(lhs.get(), rhs.get())))\n-    -> decltype(std::less<>{}(lhs.get(), rhs.get()))\n+constexpr auto operator<(const not_null<T>& lhs, const not_null<U>& rhs) noexcept(\n+    noexcept(std::less<>{}(lhs.get(), rhs.get()))) -> decltype(std::less<>{}(lhs.get(), rhs.get()))\n {\n     return std::less<>{}(lhs.get(), rhs.get());\n }\n\n template <class T, class U>\n-constexpr auto operator<=(const not_null<T>& lhs,\n-                const not_null<U>& rhs) noexcept(noexcept(std::less_equal<>{}(lhs.get(), rhs.get())))\n+constexpr auto\n+operator<=(const not_null<T>& lhs,\n+           const not_null<U>& rhs) noexcept(noexcept(std::less_equal<>{}(lhs.get(), rhs.get())))\n     -> decltype(std::less_equal<>{}(lhs.get(), rhs.get()))\n {\n     return std::less_equal<>{}(lhs.get(), rhs.get());\n }\n\n template <class T, class U>\n-constexpr auto operator>(const not_null<T>& lhs,\n-               const not_null<U>& rhs) noexcept(noexcept(std::greater<>{}(lhs.get(), rhs.get())))\n+constexpr auto\n+operator>(const not_null<T>& lhs,\n+          const not_null<U>& rhs) noexcept(noexcept(std::greater<>{}(lhs.get(), rhs.get())))\n     -> decltype(std::greater<>{}(lhs.get(), rhs.get()))\n {\n     return std::greater<>{}(lhs.get(), rhs.get());\n }\n\n template <class T, class U>\n-constexpr auto operator>=(const not_null<T>& lhs,\n-                const not_null<U>& rhs) noexcept(noexcept(std::greater_equal<>{}(lhs.get(), rhs.get())))\n+constexpr auto\n+operator>=(const not_null<T>& lhs,\n+           const not_null<U>& rhs) noexcept(noexcept(std::greater_equal<>{}(lhs.get(), rhs.get())))\n     -> decltype(std::greater_equal<>{}(lhs.get(), rhs.get()))\n {\n     return std::greater_equal<>{}(lhs.get(), rhs.get());\n@@ -230,9 +239,10 @@ not_null<T> operator+(const not_null<T>&, std::ptrdiff_t) = delete;\n template <class T>\n not_null<T> operator+(std::ptrdiff_t, const not_null<T>&) = delete;\n\n-\n-// T is conceptually a pointer so we don't have to worry about it being a reference and violating std::hash requirements\n-template <class T, class U = typename T::element_type, bool = std::is_default_constructible<std::hash<U>>::value>\n+// T is conceptually a pointer so we don't have to worry about it being a reference and violating\n+// std::hash requirements\n+template <class T, class U = typename T::element_type,\n+          bool = std::is_default_constructible<std::hash<U>>::value>\n struct not_null_hash\n {\n     std::size_t operator()(const T& value) const noexcept { return std::hash<U>{}(value.get()); }\n@@ -282,20 +292,26 @@ class strict_not_null : public not_null<T>\n {\n public:\n     template <typename U, typename = std::enable_if_t<std::is_convertible<U, T>::value>>\n-    constexpr explicit strict_not_null(U&& u) noexcept(std::is_nothrow_move_constructible<T>::value) : not_null<T>(std::forward<U>(u))\n+    constexpr explicit strict_not_null(U&& u) noexcept(std::is_nothrow_move_constructible<T>::value)\n+        : not_null<T>(std::forward<U>(u))\n     {}\n\n     template <typename = std::enable_if_t<!std::is_same<std::nullptr_t, T>::value>>\n-    constexpr explicit strict_not_null(T u) noexcept(std::is_nothrow_move_constructible<T>::value) : not_null<T>(std::move(u))\n+    constexpr explicit strict_not_null(T u) noexcept(std::is_nothrow_move_constructible<T>::value)\n+        : not_null<T>(std::move(u))\n     {}\n\n     template <typename U, typename = std::enable_if_t<std::is_convertible<U, T>::value>>\n-    constexpr strict_not_null(const strict_not_null<U>& other) noexcept(std::is_nothrow_move_constructible<T>::value) : not_null<T>(other)\n+    constexpr strict_not_null(const strict_not_null<U>& other) noexcept(\n+        std::is_nothrow_move_constructible<T>::value)\n+        : not_null<T>(other)\n     {}\n\n     // To avoid invalidating the \"not null\" invariant, the contained pointer is actually copied\n-    // instead of moved. If it is a custom pointer, its constructor could in theory throw exceptions.\n-    strict_not_null(strict_not_null&& other) noexcept(std::is_nothrow_copy_constructible<T>::value) = default;\n+    // instead of moved. If it is a custom pointer, its constructor could in theory throw\n+    // exceptions.\n+    strict_not_null(strict_not_null&& other) noexcept(\n+        std::is_nothrow_copy_constructible<T>::value) = default;\n     strict_not_null(const strict_not_null& other) = default;\n     strict_not_null& operator=(const strict_not_null& other) = default;\n\n@@ -359,6 +375,11 @@ namespace util {\n /// The C++ language provides raw references for this use case, and the C++\n /// standard library provides std::reference_wrapper, where raw references can\n /// not be used.\n+///\n+/// This type is currently not movable, meaning that the inner pointer must be\n+/// copied for any move or copy operation, such that the moved-from pointer\n+/// remains a valid NotNull pointer. Thus, NotNullUniquePtr can not be moved,\n+/// because a unique pointer can not be copied.\n template <class T>\n struct NotNull : public gsl_detail::strict_not_null<T> {\n     using gsl_detail::strict_not_null<T>::strict_not_null;\n```"
  },
  {
   "t": "2026-06-16T17:13:13Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/util/pointers.h",
   "commit": "fabc56dc9d1687a4a2a867d2c10a4a8fda4f73ff",
   "in_reply_to": 3293165660,
   "text": "Long answer:\n\n[quoted text omitted]\nCorrect, but this is harmless, because actual move-construction will correctly fail at compile time. So the only difference here is a cleaner error message.\n\nLooking at your diff, you seem to be `=delete` ing the move ctor. This is fine, because it changes the compile error message to something more explicit. Also, the comment above the `=delete` has some rationale and recommended workarounds.\n\nHowever, personally, I'd rather move toward making it movable (https://github.com/bitcoin/bitcoin/pull/34844#issuecomment-4290176661) instead of adding code that will be removed anyway when the switch is done. I already have the commit for this and I am happy to push it. At this point, it may be easier to just review one final state (movable) instead of two states?"
  },
  {
   "t": "2026-06-16T17:19:01Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/net.cpp",
   "commit": "77773e8c856fff45ad432edfe120eb13a6be95a6",
   "in_reply_to": 3292698202,
   "text": "[quoted text omitted]\n\nThis should be true for all pointer types, because the constructors are intentionally marked `explicit`. The rationale is that the constructor acts like an `Assert` (and calls `Assert`).\n\nAlso, raw pointers should just use reference_wrapper, not this class. Happy to add checks for smart pointer types, if you want.\n\n[quoted text omitted]\nSo this is intentional and shouldn't be changed in the future :sweat_smile:"
  },
  {
   "t": "2026-06-16T17:48:17Z",
   "kind": "force_push",
   "who": "maflcko",
   "commit": "fa951b422c43d7a3b51aaefbaf25f3d9d53e1a69"
  },
  {
   "t": "2026-06-16T17:48:23Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/net.cpp",
   "commit": "77773e8c856fff45ad432edfe120eb13a6be95a6",
   "in_reply_to": 3292698202,
   "text": "[quoted text omitted]\n\nAdded a unit test to document this, and the recommended workaround of taking a reference while holding the lock."
  },
  {
   "t": "2026-06-16T21:40:55Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/util/pointers.h",
   "commit": "fabc56dc9d1687a4a2a867d2c10a4a8fda4f73ff",
   "in_reply_to": 3293165660,
   "text": "[quoted text omitted]\n\nI'm fine with both - though I'm still not sure how move can be done safely with non-null source."
  },
  {
   "t": "2026-06-17T08:45:31Z",
   "kind": "comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "text": "lightly tested diff review ACK fa951b422c43d7a3b51aaefbaf25f3d9d53e1a69"
  },
  {
   "t": "2026-06-18T14:58:23Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/net.cpp",
   "commit": "77773e8c856fff45ad432edfe120eb13a6be95a6",
   "in_reply_to": 3292698202,
   "text": "I guess this thread can be resolved? The only remaining thing could be to apply the diff (https://github.com/bitcoin/bitcoin/pull/34844#discussion_r3305943200) in the future?"
  },
  {
   "t": "2026-06-18T14:58:33Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/util/pointers.h",
   "commit": "fabc56dc9d1687a4a2a867d2c10a4a8fda4f73ff",
   "in_reply_to": 3293165660,
   "text": "It can't be done safely in the language itself (unless C++29 gets trivial reloc or so). However, with clang-tidy use-after-move, this should be safe.\n\nSo I went ahead and pushed the move commmits."
  },
  {
   "t": "2026-06-18T15:05:01Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/dbwrapper.h",
   "commit": "fa0e12e77bb90bef05bfd8a960e1a28673b575be",
   "in_reply_to": 3293173669,
   "text": "[quoted text omitted]\n\nHeh, I never know when to include or not include. I guess I just try to preserve whoever wrote the bulk of the commit initially.\n\nI guess I can close this thread and the diffs can be submitted later?"
  },
  {
   "t": "2026-06-21T13:05:20Z",
   "kind": "comment",
   "who": "sedited",
   "assoc": "MEMBER",
   "text": "Concept ACK"
  },
  {
   "t": "2026-07-03T10:14:14Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/util/pointers.h",
   "commit": "fabc56dc9d1687a4a2a867d2c10a4a8fda4f73ff",
   "in_reply_to": 3293165660,
   "text": "(closing this thread, based on the thumbs-up)"
  },
  {
   "t": "2026-07-03T19:49:45Z",
   "kind": "comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "text": "The new move changes are a bit surprising to me at first glance, but I'll circle back to this later, unless you think it's urgent."
  },
  {
   "t": "2026-07-09T11:23:55Z",
   "kind": "force_push",
   "who": "maflcko",
   "commit": "fa53bbcb112b5fabf12a0f02622bc890a3bcd5fb"
  },
  {
   "t": "2026-07-09T12:02:52Z",
   "kind": "comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "text": "rebased and added a commit"
  },
  {
   "t": "2026-07-10T14:03:32Z",
   "kind": "review_comment",
   "who": "stickies-v",
   "assoc": "MEMBER",
   "path": "src/util/pointers.h",
   "commit": "fa53bbcb112b5fabf12a0f02622bc890a3bcd5fb",
   "in_reply_to": null,
   "text": "if we use the public interface here, we can skip the friend class _(, and it might (not sure) help with some use-after-move detection by marking `other` as moved, instead of just its `ptr_`?)_\n\ngit diff on fa53bbcb11\n\n```diff\ndiff --git a/src/util/pointers.h b/src/util/pointers.h\nindex a138e94624..ce8f06ab5f 100644\n--- a/src/util/pointers.h\n+++ b/src/util/pointers.h\n@@ -136,7 +136,7 @@ public:\n     {}\n\n     template <typename U, typename = std::enable_if_t<std::is_convertible<U, T>::value>>\n-    constexpr not_null(not_null<U>&& other) noexcept(std::is_nothrow_move_constructible<T>::value) : ptr_(std::move(other.ptr_))\n+    constexpr not_null(not_null<U>&& other) noexcept(std::is_nothrow_move_constructible<T>::value) : ptr_(std::move(other).get())\n     {}\n\n     constexpr not_null(const not_null& other) = default;\n@@ -172,7 +172,6 @@ public:\n     void swap(not_null<T>& other) noexcept { std::swap(ptr_, other.ptr_); }\n\n private:\n-    template <class U> friend class not_null;\n     T ptr_;\n };\n\n```"
  },
  {
   "t": "2026-07-10T14:39:05Z",
   "kind": "review_comment",
   "who": "stickies-v",
   "assoc": "MEMBER",
   "path": "src/util/pointers.h",
   "commit": "fa53bbcb112b5fabf12a0f02622bc890a3bcd5fb",
   "in_reply_to": null,
   "text": "It's not ideal (but I think the best choice out of the options) that this class relies on clang-tidy for its safe usage wrt `use-after-move`. I think one easy and free (in release) belt-and-suspenders check we can add is to `Assume` the invariant where we can?\n\ngit diff on fa53bbcb11\n\n```diff\ndiff --git a/src/util/pointers.h b/src/util/pointers.h\nindex a138e94624..589c6f4d44 100644\n--- a/src/util/pointers.h\n+++ b/src/util/pointers.h\n@@ -147,9 +147,9 @@ public:\n     constexpr details::value_or_reference_return_t<T> get() const &\n         noexcept(noexcept(details::value_or_reference_return_t<T>(std::declval<T&>())))\n     {\n-        return ptr_;\n+        return Assume(ptr_);\n     }\n-    constexpr T&& get() && noexcept { return std::move(ptr_); }\n+    constexpr T&& get() && noexcept { return std::move(Assume(ptr_)); }\n\n     constexpr operator T() const & { return get(); }\n     constexpr operator T() && noexcept { return std::move(*this).get(); }\n\n```"
  },
  {
   "t": "2026-07-10T14:48:14Z",
   "kind": "review_comment",
   "who": "stickies-v",
   "assoc": "MEMBER",
   "path": "src/util/pointers.h",
   "commit": "fa0e12e77bb90bef05bfd8a960e1a28673b575be",
   "in_reply_to": null,
   "text": "I'm not sure using gsl as a base makes sense for us. It seems very unlikely that this will ever be standardized, and we're already customizing it here quite a bit already. It seems like we don't care about the `strict_not_null` vs `not_null` separation, so we might as well avoid the complexity altogether?\n\nI [prototyped](https://github.com/bitcoin/bitcoin/compare/master...stickies-v:bitcoin:2026-07/34844-simplifications) one such approach with claude (may still be missing edge cases etc, but overall looks pretty complete to me):\n- https://github.com/stickies-v/bitcoin/commit/3c10a9588ea12f081e1dc933e9f49291e44d8395 implements from scratch, with a much smaller diff. Slightly different behaviour, e.g. `get()` doesn't have the `value_or_reference_return_t` optimization, but since we don't want this to be used for raw pointers I think that's okay?\n- https://github.com/stickies-v/bitcoin/commit/5a99e4461ae628094e3665ad78f00a7560654d48 then goes a step further and prevents raw pointers from being used, since we can be as opinionated as we like on our own types.\n\n_(note: not sure if we should/need to better attribute gsl, i just removed the copyright notice for now as it's a rewrite)_"
  },
  {
   "t": "2026-07-17T10:32:38Z",
   "kind": "force_push",
   "who": "maflcko",
   "commit": "fa3faffc76e916b162432ac7e1740761155ffeec"
  },
  {
   "t": "2026-07-17T10:32:59Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/util/pointers.h",
   "commit": "fa0e12e77bb90bef05bfd8a960e1a28673b575be",
   "in_reply_to": 3559804403,
   "text": "Yeah, I liked the gsl header-only version and was hoping it is battle-tested, but given that (1) I had to fix-up some `noexecpt` edge-cases upstream, (2) we are making it movable, (3) we likely want to easily and freely fix it up ourselves, (4) the code should be easy to read in one go, and not as a patch on top of something else, it makes sense to just write a minimal, clean, and C++20 impl ourselves.\n\nSo I went over all lines in your branch, applied some small test fixups, and then pushed it here. Added you as co-author, as you pushed the commit/diff, but let me know if you prefer to be dropped as co-author."
  },
  {
   "t": "2026-07-17T10:33:19Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/util/pointers.h",
   "commit": "fa53bbcb112b5fabf12a0f02622bc890a3bcd5fb",
   "in_reply_to": 3559732939,
   "text": "thx, added `Assume`"
  },
  {
   "t": "2026-07-17T10:34:59Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/util/pointers.h",
   "commit": "fa53bbcb112b5fabf12a0f02622bc890a3bcd5fb",
   "in_reply_to": 3559496448,
   "text": "I think `other` is already moved, so it shouldn't change any detection. But I pushed your branch, so this is now included."
  },
  {
   "t": "2026-07-17T10:53:39Z",
   "kind": "force_push",
   "who": "maflcko",
   "commit": "fac8d22952dcd623b6e5860bf91a8bea85877c49"
  },
  {
   "t": "2026-07-17T11:07:19Z",
   "kind": "force_push",
   "who": "maflcko",
   "commit": "fa0227405c497e94ddb7a7603b37993a7703c0c1"
  },
  {
   "t": "2026-07-21T15:34:10Z",
   "kind": "review_comment",
   "who": "stickies-v",
   "assoc": "MEMBER",
   "path": "src/util/pointers.h",
   "commit": "fa6228d332063f3345925ede050d61480a09aab6",
   "in_reply_to": null,
   "text": "nit: I think we typically implement these as member functions\n\ngit diff on fa0227405c\n\n```diff\ndiff --git a/src/util/pointers.h b/src/util/pointers.h\nindex 442ae7c160..51300c2ce9 100644\n--- a/src/util/pointers.h\n+++ b/src/util/pointers.h\n@@ -80,6 +80,11 @@ public:\n\n     void swap(NotNull& other) noexcept { std::swap(m_ptr, other.m_ptr); }\n\n+    template <class U>\n+    constexpr auto operator<=>(const NotNull<U>& other) const { return std::compare_three_way{}(get(), other.get()); }\n+    template <class U>\n+    constexpr bool operator==(const NotNull<U>& other) const { return get() == other.get(); }\n+\n private:\n     T m_ptr;\n };\n@@ -93,18 +98,6 @@ void swap(NotNull<T>& a, NotNull<T>& b) noexcept\n     a.swap(b);\n }\n\n-template <class T, class U>\n-constexpr auto operator<=>(const NotNull<T>& a, const NotNull<U>& b)\n-{\n-    return std::compare_three_way{}(a.get(), b.get());\n-}\n-\n-template <class T, class U>\n-constexpr bool operator==(const NotNull<T>& a, const NotNull<U>& b)\n-{\n-    return a.get() == b.get();\n-}\n-\n template <typename T, typename Deleter = std::default_delete<T>>\n using NotNullUniquePtr = NotNull<std::unique_ptr<T, Deleter>>;\n\n```"
  },
  {
   "t": "2026-07-21T16:00:44Z",
   "kind": "review_comment",
   "who": "stickies-v",
   "assoc": "MEMBER",
   "path": "src/util/pointers.h",
   "commit": "fa6228d332063f3345925ede050d61480a09aab6",
   "in_reply_to": null,
   "text": "nit: `swap` is currently unused, might make sense to add if/when we need it?"
  },
  {
   "t": "2026-07-21T16:18:31Z",
   "kind": "review_comment",
   "who": "stickies-v",
   "assoc": "MEMBER",
   "path": "src/net.cpp",
   "commit": "fa0e12e77bb90bef05bfd8a960e1a28673b575be",
   "in_reply_to": null,
   "text": "fa27f2e49fe6875769477f536735607c55140e16 nit: why not do this in the previous commit right away?"
  },
  {
   "t": "2026-07-22T08:38:33Z",
   "kind": "force_push",
   "who": "maflcko",
   "commit": "fa478f9ce2e2ca44df2db2ae9fe2276729f4adb5"
  },
  {
   "t": "2026-07-22T08:49:10Z",
   "kind": "force_push",
   "who": "maflcko",
   "commit": "fa6228d332063f3345925ede050d61480a09aab6"
  },
  {
   "t": "2026-07-22T11:01:37Z",
   "kind": "review_comment",
   "who": "stickies-v",
   "assoc": "MEMBER",
   "path": "src/test/rbf_tests.cpp",
   "commit": "fa6228d332063f3345925ede050d61480a09aab6",
   "in_reply_to": null,
   "text": "It increases the diff, but I think using scopes here would be the better choice, instead of documenting how to abuse `NotNull`."
  },
  {
   "t": "2026-07-22T11:30:26Z",
   "kind": "review_comment",
   "who": "stickies-v",
   "assoc": "MEMBER",
   "path": "src/util/pointers.h",
   "commit": "fa6228d332063f3345925ede050d61480a09aab6",
   "in_reply_to": null,
   "text": "nit: iiuc `use-after-move` static analysis does not catch everything, so perhaps \"can never be observed\" is misleading / too strong."
  },
  {
   "t": "2026-07-22T14:11:25Z",
   "kind": "review_comment",
   "who": "stickies-v",
   "assoc": "MEMBER",
   "path": "src/util/pointers.h",
   "commit": "fa6228d332063f3345925ede050d61480a09aab6",
   "in_reply_to": null,
   "text": "nit: could do with some `LIFETIMEBOUND`:\n\ngit diff on fa0227405c\n\n```diff\ndiff --git a/src/util/pointers.h b/src/util/pointers.h\nindex 442ae7c160..9fcfae6bc8 100644\n--- a/src/util/pointers.h\n+++ b/src/util/pointers.h\n@@ -5,6 +5,7 @@\n #ifndef BITCOIN_UTIL_POINTERS_H\n #define BITCOIN_UTIL_POINTERS_H\n\n+#include <attributes.h>\n #include <util/check.h>\n\n #include <compare>\n@@ -69,10 +70,10 @@ public:\n     constexpr NotNull(const NotNull&) = default;\n     constexpr NotNull& operator=(const NotNull&) = default;\n\n-    constexpr const T& get() const& noexcept { return Assume(m_ptr); }\n-    constexpr T&& get() && noexcept { return std::move(Assume(m_ptr)); }\n+    constexpr const T& get() const& noexcept LIFETIMEBOUND { return Assume(m_ptr); }\n+    constexpr T&& get() && noexcept LIFETIMEBOUND { return std::move(Assume(m_ptr)); }\n\n-    constexpr decltype(auto) operator->() const { return get(); }\n+    constexpr decltype(auto) operator->() const LIFETIMEBOUND { return get(); }\n     constexpr decltype(auto) operator*() const { return *get(); }\n\n     constexpr operator T() const& { return get(); }\n\n```"
  },
  {
   "t": "2026-07-22T15:23:38Z",
   "kind": "review_comment",
   "who": "stickies-v",
   "assoc": "MEMBER",
   "path": "src/util/pointers.h",
   "commit": "fa0e12e77bb90bef05bfd8a960e1a28673b575be",
   "in_reply_to": null,
   "text": "For a non-const lvalue `NotNull<T>` under direct-init, the forwarding ctor is selected instead of the copy ctor.\n\n```\nNotNull<std::shared_ptr<int>> t{std::make_shared<int>(1)};\nNotNull<std::shared_ptr<int>> u{t};\n```\n\ngit diff on fa0227405c\n\n```diff\ndiff --git a/src/util/pointers.h b/src/util/pointers.h\nindex 442ae7c160..9325dd1e69 100644\n--- a/src/util/pointers.h\n+++ b/src/util/pointers.h\n@@ -43,6 +43,7 @@ class NotNull\n {\n public:\n     template <std::convertible_to<T> U>\n+        requires (!std::same_as<std::remove_cvref_t<U>, NotNull>)\n     constexpr explicit NotNull(U&& u) noexcept(std::is_nothrow_constructible_v<T, U&&>)\n         : m_ptr(std::forward<U>(u))\n     {\n\n```"
  },
  {
   "t": "2026-07-22T16:05:36Z",
   "kind": "review",
   "who": "stickies-v",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "fa6228d332063f3345925ede050d61480a09aab6",
   "text": "Approach ACK, code lgtm fa0227405c497e94ddb7a7603b37993a7703c0c1 but the [forwarding ctor issue](https://github.com/bitcoin/bitcoin/pull/34844#discussion_r3631508842) should probably be fixed, nothing else blocking"
  },
  {
   "t": "2026-08-02T13:52:29Z",
   "kind": "force_push",
   "who": "maflcko",
   "commit": "fa0e12e77bb90bef05bfd8a960e1a28673b575be"
  },
  {
   "t": "2026-08-02T13:56:26Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/util/pointers.h",
   "commit": "fa0e12e77bb90bef05bfd8a960e1a28673b575be",
   "in_reply_to": 3631508842,
   "text": "Sure, but there can't be an issue? `operator T` creates a copy, just like `.get()` creates a copy. And any compiler failure before will remain a compile failure after?\n\nI guess you are mostly worried about this style-wise and for trait stuff like:\n\n`std::is_constructible_v<NotNullUniquePtr<int>, NotNullUniquePtr<int>&>` ?"
  },
  {
   "t": "2026-08-02T13:56:28Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/util/pointers.h",
   "commit": "fa6228d332063f3345925ede050d61480a09aab6",
   "in_reply_to": 3630929058,
   "text": "thx, done for all references"
  },
  {
   "t": "2026-08-02T13:56:31Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/util/pointers.h",
   "commit": "fa6228d332063f3345925ede050d61480a09aab6",
   "in_reply_to": 3629828909,
   "text": "thx, `s/can never/should never/`"
  },
  {
   "t": "2026-08-02T13:56:35Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/test/rbf_tests.cpp",
   "commit": "fa6228d332063f3345925ede050d61480a09aab6",
   "in_reply_to": 3629666468,
   "text": "Yeah, that is also less code and a cleaner. Thx, done."
  },
  {
   "t": "2026-08-02T13:56:38Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/net.cpp",
   "commit": "fa0e12e77bb90bef05bfd8a960e1a28673b575be",
   "in_reply_to": 3623817342,
   "text": "thx, squashed the two commits"
  },
  {
   "t": "2026-08-02T13:56:43Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/util/pointers.h",
   "commit": "fa6228d332063f3345925ede050d61480a09aab6",
   "in_reply_to": 3623493707,
   "text": "thx, done"
  },
  {
   "t": "2026-08-02T13:56:43Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/util/pointers.h",
   "commit": "fa6228d332063f3345925ede050d61480a09aab6",
   "in_reply_to": 3623680491,
   "text": "Hmm, I guess move is noexcept for the smart pointers we care about, so we can just use the default swap fallback and let the compiler optimize down to the same binary.\n\nRemoved."
  },
  {
   "t": "2026-08-03T08:59:21Z",
   "kind": "review_comment",
   "who": "stickies-v",
   "assoc": "MEMBER",
   "path": "src/util/pointers.h",
   "commit": "fa0e12e77bb90bef05bfd8a960e1a28673b575be",
   "in_reply_to": 3631508842,
   "text": "Sorry, should have stated why I raised this. My main concern was the ambiguity this creates, i.e. `NotNull<std::shared_ptr<int>> u{t};` and `NotNull<std::shared_ptr<int>> u = t;` use different constructors, with the former adding an extra `Assert`. Indeed this shouldn't lead to any issues, except for when we're dealing with a moved-from object (which shouldn't be possible, but is not guaranteed), in which case they will have different behaviour. I think it's prudent to prevent this from happening.\n\nI also assumed the copy constructor would be more performant, but thinking about it again I suppose the difference is negligible, with just the (very) minimal runtime `Assert` overhead."
  },
  {
   "t": "2026-08-03T09:56:26Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/util/pointers.h",
   "commit": "fa0e12e77bb90bef05bfd8a960e1a28673b575be",
   "in_reply_to": 3631508842,
   "text": "Ok, the additional assert makes sense. My eyes didn't see that, and I guess compilers won't optimize it away either, unless there are annotations on the getter/opT(), see https://github.com/bitcoin/bitcoin/issues/24423#issuecomment-4079986901\n\nIn any case, I've applied your diff, so this should be fixed."
  },
  {
   "t": "2026-08-03T17:07:47Z",
   "kind": "review",
   "who": "stickies-v",
   "assoc": "MEMBER",
   "state": "APPROVED",
   "commit": "fa0e12e77bb90bef05bfd8a960e1a28673b575be",
   "text": "ACK fa0e12e77bb90bef05bfd8a960e1a28673b575be"
  }
 ],
 "labels_log": [
  {
   "t": "2026-03-17T19:42:56Z",
   "action": "labeled",
   "label": "Utils/log/libs",
   "who": "DrahtBot"
  },
  {
   "t": "2026-03-17T20:03:45Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-03-17T22:17:50Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-03-24T08:40:24Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-03-24T09:24:52Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-04-03T12:03:11Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-04-03T12:45:03Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-04-21T15:16:44Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-04-21T16:07:35Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-06-16T09:40:43Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-06-16T11:23:39Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-09T09:24:27Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-09T11:48:28Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-14T21:23:13Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-17T10:54:15Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-17T11:29:26Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-17T12:31:36Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-21T23:45:25Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-22T08:49:42Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-22T09:55:56Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-22T11:00:38Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  }
 ],
 "state_log": [],
 "text_chars": 91335,
 "text_tokens_estimate": 22833,
 "changed_paths": [
  "src/coins.h",
  "src/dbwrapper.h",
  "src/net.cpp",
  "src/net.h",
  "src/test/CMakeLists.txt",
  "src/test/coinsviewoverlay_tests.cpp",
  "src/test/fuzz/coins_view.cpp",
  "src/test/fuzz/coinscache_sim.cpp",
  "src/test/rbf_tests.cpp",
  "src/test/util_pointers_tests.cpp",
  "src/txmempool.h",
  "src/util/pointers.h",
  "src/validation.cpp"
 ],
 "files": [
  {
   "path": "src/coins.h",
   "add": 4,
   "del": 4
  },
  {
   "path": "src/dbwrapper.h",
   "add": 3,
   "del": 2
  },
  {
   "path": "src/net.cpp",
   "add": 3,
   "del": 3
  },
  {
   "path": "src/net.h",
   "add": 2,
   "del": 1
  },
  {
   "path": "src/test/CMakeLists.txt",
   "add": 1,
   "del": 0
  },
  {
   "path": "src/test/coinsviewoverlay_tests.cpp",
   "add": 4,
   "del": 4
  },
  {
   "path": "src/test/fuzz/coins_view.cpp",
   "add": 1,
   "del": 1
  },
  {
   "path": "src/test/fuzz/coinscache_sim.cpp",
   "add": 1,
   "del": 1
  },
  {
   "path": "src/test/rbf_tests.cpp",
   "add": 50,
   "del": 46
  },
  {
   "path": "src/test/util_pointers_tests.cpp",
   "add": 189,
   "del": 0
  },
  {
   "path": "src/txmempool.h",
   "add": 4,
   "del": 2
  },
  {
   "path": "src/util/pointers.h",
   "add": 117,
   "del": 0
  },
  {
   "path": "src/validation.cpp",
   "add": 1,
   "del": 1
  }
 ],
 "test_lines": 298,
 "git": {
  "head": "fa0e12e77bb90bef05bfd8a960e1a28673b575be",
  "head_matches_backup": true,
  "base": "67efced1fc83a0b7215cc1513e7c4754fee0f12f",
  "commits": [
   {
    "sha": "fa3bda5fb8",
    "subject": "util: Add util::NotNull<SmartPtrType>",
    "files": 3,
    "add": 307,
    "del": 0
   },
   {
    "sha": "fac1aaacf6",
    "subject": "refactor: Use util::NotNull<std::unique_ptr<LevelDBContext>> m_db_context",
    "files": 1,
    "add": 3,
    "del": 2
   },
   {
    "sha": "fae7b4c275",
    "subject": "refactor: Use NotNull pointer to input fetching pool",
    "files": 5,
    "add": 11,
    "del": 11
   },
   {
    "sha": "fa1c90333f",
    "subject": "refactor: In CNode use util::NotNull<std::unique_ptr<Transport>> m_transport",
    "files": 2,
    "add": 5,
    "del": 4
   },
   {
    "sha": "fa0e12e77b",
    "subject": "refactor: Return util::NotNull<std::unique_ptr<ChangeSet>> from CTxMemPool::GetChangeSet()",
    "files": 2,
    "add": 54,
    "del": 48
   }
  ],
  "patch_truncated": false
 },
 "input_hash": "9d6a8233c60278a7",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}