wallet: birth time update during tx scanning #28920

pull furszy wants to merge 5 commits into bitcoin:master from furszy:2023_wallet_birhtime_update changing 7 files +140 −10
  1. furszy commented at 4:05 pm on November 20, 2023: member

    Fixing #28897.

    As the user may have imported a descriptor with a timestamp newer than the actual birth time of the first key (by setting ’timestamp=now’), the wallet needs to update the birth time when it detects a transaction older than the oldest descriptor timestamp.

    Testing Notes: Can cherry-pick the test commit on top of master. It will fail there.

  2. DrahtBot commented at 4:05 pm on November 20, 2023: contributor

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

    Code Coverage

    For detailed information about the code coverage, see the test coverage report.

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    ACK Sjors, achow101
    Approach ACK S3RK
    Stale ACK BrandonOdiwuor

    If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #26596 (wallet: Migrate legacy wallets to descriptor wallets without requiring BDB by achow101)
    • #22693 (RPC/Wallet: Add “use_txids” to output of getaddressinfo by luke-jr)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

  3. DrahtBot added the label Wallet on Nov 20, 2023
  4. DrahtBot added the label CI failed on Nov 20, 2023
  5. furszy force-pushed on Nov 20, 2023
  6. DrahtBot removed the label CI failed on Nov 20, 2023
  7. furszy force-pushed on Nov 20, 2023
  8. Sjors commented at 8:49 am on November 21, 2023: member

    Concept ACK

    You could also add a oldest_timestamp (or birth) field to getwalletinfo which would contain the birth date.

  9. in src/wallet/wallet.h:689 in 4bfb243ac4 outdated
    685@@ -686,7 +686,7 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati
    686     bool ImportScriptPubKeys(const std::string& label, const std::set<CScript>& script_pub_keys, const bool have_solving_data, const bool apply_label, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
    687 
    688     /** Updates wallet birth time if 'new_birth_time' is below it */
    689-    void FirstKeyTimeChanged(const ScriptPubKeyMan* spkm, int64_t new_birth_time);
    690+    void TryUpdateBirthTime(int64_t new_birth_time);
    


    Sjors commented at 8:50 am on November 21, 2023:
    4bfb243ac41cf1396cf55f8b24de2ba52186c7a4: alternative name: MaybeUpdateBirthTime( )

    furszy commented at 0:50 am on November 22, 2023:
    sure, updated.
  10. in src/wallet/wallet.cpp:1750 in 4bfb243ac4 outdated
    1746@@ -1747,7 +1747,7 @@ bool CWallet::ImportScriptPubKeys(const std::string& label, const std::set<CScri
    1747     return true;
    1748 }
    1749 
    1750-void CWallet::FirstKeyTimeChanged(const ScriptPubKeyMan* spkm, int64_t new_birth_time)
    1751+void CWallet::TryUpdateBirthTime(int64_t new_birth_time)
    


    Sjors commented at 8:53 am on November 21, 2023:
    4bfb243ac41cf1396cf55f8b24de2ba52186c7a4: I’m a bit puzzled why this function ever accepted an argument that it didn’t use.

    furszy commented at 11:17 pm on November 21, 2023:
    I think I had a birth time per descriptor before. This work was taken from my bip157/bip158 light client branch. Where I only use the filters that have a birth time below the header time to decrease the false-positive rate. I was thinking on porting it to the wallet rescan process when I did it. But.. I decided to leave it for when the p2p related PRs get merged. And.. #28170, #28120 and #27837 are still open.
  11. in src/wallet/wallet.cpp:3492 in 4bfb243ac4 outdated
    3482@@ -3483,7 +3483,7 @@ void CWallet::AddScriptPubKeyMan(const uint256& id, std::unique_ptr<ScriptPubKey
    3483     const auto& spkm = m_spk_managers[id] = std::move(spkm_man);
    


    Sjors commented at 8:55 am on November 21, 2023:

    4bfb243ac41cf1396cf55f8b24de2ba52186c7a4: this seems simpler:

    0TryUpdateBirthTime(spkm_man->GetTimeFirstKey());
    1m_spk_managers[id] = std::move(spkm_man);
    

    furszy commented at 1:05 am on November 22, 2023:

    4bfb243: this seems simpler:

    0TryUpdateBirthTime(spkm_man->GetTimeFirstKey());
    1m_spk_managers[id] = std::move(spkm_man);
    

    That could introduce a bug in the future if we ever add logic inside TryUpdateBirthTime() accessing m_spk_managers in some way.


    Sjors commented at 9:54 am on November 23, 2023:

    Ah, that makes sense, can you add a comment? Something like:

    0// Add spkm_man to m_spk_managers before calling any method
    1// that might access it.
    2const auto& spkm = m_spk_managers[id] = std::move(spkm_man);
    

    furszy commented at 12:56 pm on November 23, 2023:
    Sure. Done as suggested.
  12. in src/wallet/wallet.cpp:3519 in 4bfb243ac4 outdated
    3515@@ -3516,7 +3516,7 @@ void CWallet::ConnectScriptPubKeyManNotifiers()
    3516     for (const auto& spk_man : GetActiveScriptPubKeyMans()) {
    3517         spk_man->NotifyWatchonlyChanged.connect(NotifyWatchonlyChanged);
    3518         spk_man->NotifyCanGetAddressesChanged.connect(NotifyCanGetAddressesChanged);
    3519-        spk_man->NotifyFirstKeyTimeChanged.connect(std::bind(&CWallet::FirstKeyTimeChanged, this, std::placeholders::_1, std::placeholders::_2));
    3520+        spk_man->NotifyFirstKeyTimeChanged.connect(std::bind(&CWallet::TryUpdateBirthTime, this, std::placeholders::_2));
    


    Sjors commented at 9:11 am on November 21, 2023:

    4bfb243ac41cf1396cf55f8b24de2ba52186c7a4: you can use std::placeholders::_1 here, by dropping this from NotifyFirstKeyTimeChanged calls and changing:

     0--- a/src/wallet/scriptpubkeyman.h
     1+++ b/src/wallet/scriptpubkeyman.h
     2@@ -255,11 +255,11 @@ public:
     3 
     4     /** Keypool has new keys */
     5     boost::signals2::signal<void ()> NotifyCanGetAddressesChanged;
     6 
     7     /** Birth time changed */
     8-    boost::signals2::signal<void (const ScriptPubKeyMan* spkm, int64_t new_birth_time)> NotifyFirstKeyTimeChanged;
     9+    boost::signals2::signal<void (int64_t new_birth_time)> NotifyFirstKeyTimeChanged;
    10 };
    

    Passing fewer (unused) complex objects around is always better…


    furszy commented at 11:09 pm on November 21, 2023:

    I didn’t drop the spkm arg on purpose. It was a design decision. The idea is the following one: The wallet stores multiple spkms, and it registers to multiple signals from each of them. If we don’t provide the pointer to the originator spkm, we will not be able to distinguish them. And, I’m pretty confident we will need this data, or need to access the origin spkm during an event, sooner or later.

    But if you are strong on this, I could change it. We could always re-add the spkm pointer when/if we need it.


    Sjors commented at 9:51 am on November 23, 2023:

    If you have good reasons to believe we need these events later, then keeping them is fine by me. But why do you think this?

    Presumable related to #28920 (review)


    furszy commented at 1:06 pm on November 23, 2023:
    Yeah, I was thinking about #28920 (review) and other features I have in mind. But, more generally, I believe the wallet should be aware of the source of the event. However, upon further thought, we could achieve this by creating multiple handlers on the wallet side instead of passing the origin pointer to the signal itself.
  13. Sjors commented at 9:19 am on November 21, 2023: member
    I checked that the test fails if you drop everything but 134960c3828abe658d565af52e3c6c5af03019f2.
  14. furszy force-pushed on Nov 22, 2023
  15. furszy commented at 0:58 am on November 22, 2023: member

    Updated per feedback, thanks @Sjors.

    You could also add a oldest_timestamp (or birth) field to getwalletinfo which would contain the birth date.

    Sounds good 👌🏼. Added it in another commit to not disable the possibility of verifying the failure by cherry-picking the test commit on top of master.

  16. refactor: rename FirstKeyTimeChanged to MaybeUpdateBirthTime
    In the following-up commit, the wallet birth time will also
    be modified by the transactions scanning process. When a tx
    older than all descriptor's timestamp is detected.
    b4306e3c8d
  17. wallet: birth time update during tx scanning
    As the user could have imported a descriptor with
    a newer timestamp (by blindly setting 'timestamp=now'),
    the wallet needs to update the birth time when it detects
    a transaction older than the oldest descriptor timestamp.
    75fbf444c1
  18. furszy force-pushed on Nov 23, 2023
  19. furszy commented at 1:07 pm on November 23, 2023: member
    Updated per feedback. Tiny diff. Only added a small explanatory comment.
  20. in test/functional/wallet_reindex.py:40 in 25de6ff824 outdated
    35+    def birthtime_test(self, node, miner_wallet):
    36+        self.log.info("Test birth time update during tx scanning")
    37+        # Fund address to test
    38+        wallet_addr = miner_wallet.getnewaddress()
    39+        tx_id = miner_wallet.sendtoaddress(wallet_addr, 2)
    40+        miner_wallet.syncwithvalidationinterfacequeue()
    


    maflcko commented at 3:10 pm on November 24, 2023:
    q: Is this needed? It should be called internally for most stuff that needs it.

    furszy commented at 4:21 pm on November 24, 2023:

    q: Is this needed? It should be called internally for most stuff that needs it.

    Good eye. No, it is not needed. I was probably worried about the tx not reaching the mempool prior to generating block. But.. sendtoaddress (same as every other send mechanism) always reaches the mempool. Returning a mempool error if it fails to get added there. And.. this is independent to the validation interface queue. So, I’m not sure why my past-self added it.


    furszy commented at 4:23 pm on November 24, 2023:
    Pushed it. Thanks.
  21. furszy force-pushed on Nov 24, 2023
  22. BrandonOdiwuor commented at 2:18 pm on November 27, 2023: contributor
    Concept ACK
  23. Sjors commented at 1:49 pm on November 28, 2023: member
    ACK 8cd2fedb3ab283f9dbabd8cf1827e63f4f0db8d7
  24. DrahtBot requested review from BrandonOdiwuor on Nov 28, 2023
  25. in test/functional/wallet_reindex.py:47 in 8cd2fedb3a outdated
    42+        for _ in range(50):
    43+            self.generate(node, 1)
    44+            self.advance_time(node, BLOCK_TIME)
    45+
    46+        # Now create a new wallet, and import the descriptor
    47+        node.createwallet(wallet_name='watch_only', disable_private_keys=True, blank=True, load_on_startup=True)
    


    BrandonOdiwuor commented at 2:17 pm on November 28, 2023:
    0        node.createwallet(wallet_name='watch_only', disable_private_keys=True, load_on_startup=True)
    

    nit: blank is not necessary.


    furszy commented at 9:05 pm on November 28, 2023:
    Done
  26. BrandonOdiwuor approved
  27. BrandonOdiwuor commented at 2:17 pm on November 28, 2023: contributor

    ACK 8cd2fedb3ab283f9dbabd8cf1827e63f4f0db8d7

    left a nit comment

  28. in test/functional/test_runner.py:210 in bc8fb2a9d5 outdated
    205@@ -206,6 +206,7 @@
    206     'wallet_createwallet.py --descriptors',
    207     'wallet_watchonly.py --legacy-wallet',
    208     'wallet_watchonly.py --usecli --legacy-wallet',
    209+    'wallet_reindex.py',
    


    achow101 commented at 2:43 pm on November 28, 2023:

    In bc8fb2a9d5fe941bcc77eacb23d72c663828f8e3 “test: coverage for wallet birth time interaction with -reindex”

    Needs --legacy-wallet and --descriptor.


    furszy commented at 9:04 pm on November 28, 2023:
    Done. Plus, discovered and fixed an issue with the legacy wallet birth time along the way.
  29. furszy force-pushed on Nov 28, 2023
  30. furszy commented at 9:19 pm on November 28, 2023: member

    Updated per feedback, thanks for the reviews.

    While updating the test suite to incorporate the --legacy-wallet test counterpart, discovered an issue with the first key time in blank legacy wallets. It was incorrectly set to 0 (aka scan from the beginning of time) instead of being set to the maximum timestamp value. Therefore, in master, blank legacy wallets are currently scanning the entire chain instead of utilizing the birth time functionality. Added 0a1107d to fix the issue.

  31. furszy force-pushed on Nov 28, 2023
  32. furszy force-pushed on Nov 28, 2023
  33. DrahtBot added the label CI failed on Nov 28, 2023
  34. DrahtBot removed the label CI failed on Nov 29, 2023
  35. in src/wallet/rpc/wallet.cpp:136 in a41393d329 outdated
    132@@ -132,6 +133,9 @@ static RPCHelpMan getwalletinfo()
    133     obj.pushKV("descriptors", pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS));
    134     obj.pushKV("external_signer", pwallet->IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER));
    135     obj.pushKV("blank", pwallet->IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET));
    136+    if (int64_t birthtime = pwallet->GetBirthTime(); birthtime != std::numeric_limits<int64_t>::max()) {
    


    luke-jr commented at 1:29 am on December 5, 2023:
    Shouldn’t this be NO_KEY_TIME explicitly?

    furszy commented at 8:05 pm on December 5, 2023:

    yeah, it could be NO_KEY_TIME or could also return an optional.

    The issue that I see with the NO_KEY_TIME constant is where to place it. Because, if we place it at the wallet.h level, we would add a circular dependency wallet -> spkm -> wallet.


    achow101 commented at 9:31 pm on December 5, 2023:
    Agree that this should be NO_KEY_TIME. Since wallet already includes spkm, leaving it there will not be circular, and it can still be accessed by things that include wallet. It just needs to be pulled out of LegacyScriptPubKeyMan.

    furszy commented at 9:59 pm on December 5, 2023:
    Done. Updated.
  36. in src/wallet/scriptpubkeyman.h:290 in a41393d329 outdated
    285@@ -286,7 +286,9 @@ class LegacyScriptPubKeyMan : public ScriptPubKeyMan, public FillableSigningProv
    286     WatchOnlySet setWatchOnly GUARDED_BY(cs_KeyStore);
    287     WatchKeyMap mapWatchKeys GUARDED_BY(cs_KeyStore);
    288 
    289-    int64_t nTimeFirstKey GUARDED_BY(cs_KeyStore) = 0;
    290+    // By default, do not scan any block until keys/scripts are generated/imported
    291+    int64_t NO_KEY_TIME = std::numeric_limits<int64_t>::max();
    


    luke-jr commented at 1:29 am on December 5, 2023:
    Seems like NO_KEY_TIME ought to be 0 so it triggers a full scan

    furszy commented at 8:10 pm on December 5, 2023:

    Seems like NO_KEY_TIME ought to be 0 so it triggers a full scan

    That was the bug I solved 0a1107d0. By default, the legacy spkm is empty. It has no keys nor scripts, thus it should not trigger a full scan. Once the first key is generated or the first script is imported, the legacy spkm will update the nTimeFirstKey time automatically and start scanning blocks.

  37. luke-jr changes_requested
  38. wallet: fix legacy spkm default birth time
    To avoid scanning blocks, as assumed by a wallet with no
    generated keys or imported scripts, the default value for
    the birth time needs to be set to the maximum int64_t value.
    
    Once the first key is generated or the first script is imported,
    the legacy SPKM will update the birth time automatically.
    6f497377aa
  39. test: coverage for wallet birth time interaction with -reindex
    Verifying the wallet updates the birth time accordingly when it
    detects a transaction with a time older than the oldest descriptor
    timestamp.
    This could happen when the user blindly imports a descriptor with
    'timestamp=now'.
    83c66444d0
  40. rpc: getwalletinfo, return wallet 'birthtime'
    And add coverage for it
    1ce45baed7
  41. furszy force-pushed on Dec 5, 2023
  42. furszy commented at 2:48 pm on December 8, 2023: member
    Updated per feedback, small diff. Thanks. Ready to go. Renamed NO_KEY_TIME to UNKNOWN_TIME and made it accessible for other modules.
  43. S3RK commented at 9:06 am on December 11, 2023: contributor
    Approach ACK
  44. Sjors commented at 1:12 pm on December 14, 2023: member

    re-utACK 1ce45baed7dd2da3f1cb85c9c25110e5537451ae

    Should we backport 6f497377aa17cb8a590fd7717fa8ededf4249999 or the whole PR, and if so how far?

  45. DrahtBot requested review from BrandonOdiwuor on Dec 14, 2023
  46. DrahtBot requested review from S3RK on Dec 14, 2023
  47. DrahtBot removed review request from BrandonOdiwuor on Dec 14, 2023
  48. DrahtBot requested review from BrandonOdiwuor on Dec 14, 2023
  49. achow101 added the label Needs backport (26.x) on Dec 14, 2023
  50. achow101 commented at 8:48 pm on December 14, 2023: member

    Should we backport 6f49737 or the whole PR, and if so how far?

    I think it would be good to backport the entire thing to 26.1.

  51. DrahtBot removed review request from BrandonOdiwuor on Dec 14, 2023
  52. DrahtBot requested review from BrandonOdiwuor on Dec 14, 2023
  53. DrahtBot removed review request from BrandonOdiwuor on Dec 14, 2023
  54. DrahtBot requested review from BrandonOdiwuor on Dec 14, 2023
  55. achow101 commented at 9:13 pm on December 14, 2023: member
    ACK 1ce45baed7dd2da3f1cb85c9c25110e5537451ae
  56. DrahtBot removed review request from BrandonOdiwuor on Dec 14, 2023
  57. DrahtBot requested review from BrandonOdiwuor on Dec 14, 2023
  58. achow101 merged this on Dec 14, 2023
  59. achow101 closed this on Dec 14, 2023

  60. furszy deleted the branch on Dec 14, 2023
  61. fanquake removed the label Needs backport (26.x) on Dec 15, 2023
  62. fanquake commented at 10:38 am on December 15, 2023: member
    Adding to #29011 for backporting to 26.x.
  63. fanquake referenced this in commit 5dce8966d7 on Dec 15, 2023
  64. fanquake referenced this in commit 65cd84cb86 on Dec 15, 2023
  65. fanquake referenced this in commit acc836b1f9 on Dec 15, 2023
  66. fanquake referenced this in commit f76869252b on Dec 15, 2023
  67. fanquake referenced this in commit 7e90b5938f on Dec 15, 2023

github-metadata-mirror

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

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