From 1ce45baed7dd2da3f1cb85c9c25110e5537451ae Mon Sep 17 00:00:00 2001 From: furszy Date: Tue, 21 Nov 2023 21:48:57 -0300 Subject: [PATCH] rpc: getwalletinfo, return wallet 'birthtime' And add coverage for it --- src/wallet/rpc/wallet.cpp | 4 ++++ src/wallet/wallet.h | 3 +++ test/functional/wallet_reindex.py | 25 +++++++++++++++++++++++-- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/wallet/rpc/wallet.cpp b/src/wallet/rpc/wallet.cpp index 164ce9afedb..c635093344e 100644 --- a/src/wallet/rpc/wallet.cpp +++ b/src/wallet/rpc/wallet.cpp @@ -69,6 +69,7 @@ static RPCHelpMan getwalletinfo() {RPCResult::Type::BOOL, "descriptors", "whether this wallet uses descriptors for scriptPubKey management"}, {RPCResult::Type::BOOL, "external_signer", "whether this wallet is configured to use an external signer such as a hardware wallet"}, {RPCResult::Type::BOOL, "blank", "Whether this wallet intentionally does not contain any keys, scripts, or descriptors"}, + {RPCResult::Type::NUM_TIME, "birthtime", /*optional=*/true, "The start time for blocks scanning. It could be modified by (re)importing any descriptor with an earlier timestamp."}, RESULT_LAST_PROCESSED_BLOCK, }}, }, @@ -132,6 +133,9 @@ static RPCHelpMan getwalletinfo() obj.pushKV("descriptors", pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)); obj.pushKV("external_signer", pwallet->IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER)); obj.pushKV("blank", pwallet->IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET)); + if (int64_t birthtime = pwallet->GetBirthTime(); birthtime != UNKNOWN_TIME) { + obj.pushKV("birthtime", birthtime); + } AppendLastProcessedBlock(obj, *pwallet); return obj; diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index a1bf3784c95..3fac98c898a 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -884,6 +884,9 @@ public: /* Returns true if the wallet can give out new addresses. This means it has keys in the keypool or can generate new keys */ bool CanGetAddresses(bool internal = false) const; + /* Returns the time of the first created key or, in case of an import, it could be the time of the first received transaction */ + int64_t GetBirthTime() const { return m_birth_time; } + /** * Blocks until the wallet state is up-to-date to /at least/ the current * chain at the time this function is entered diff --git a/test/functional/wallet_reindex.py b/test/functional/wallet_reindex.py index 95d5fd85ad6..5388de4b717 100755 --- a/test/functional/wallet_reindex.py +++ b/test/functional/wallet_reindex.py @@ -44,8 +44,10 @@ class WalletReindexTest(BitcoinTestFramework): self.advance_time(node, BLOCK_TIME) # Now create a new wallet, and import the descriptor - node.createwallet(wallet_name='watch_only', disable_private_keys=True, blank=True, load_on_startup=True) + node.createwallet(wallet_name='watch_only', disable_private_keys=True, load_on_startup=True) wallet_watch_only = node.get_wallet_rpc('watch_only') + # Blank wallets don't have a birth time + assert 'birthtime' not in wallet_watch_only.getwalletinfo() # For a descriptors wallet: Import address with timestamp=now. # For legacy wallet: There is no way of importing a script/address with a custom time. The wallet always imports it with birthtime=1. @@ -53,6 +55,16 @@ class WalletReindexTest(BitcoinTestFramework): wallet_watch_only.importaddress(wallet_addr, rescan=False) assert_equal(len(wallet_watch_only.listtransactions()), 0) + # Depending on the wallet type, the birth time changes. + wallet_birthtime = wallet_watch_only.getwalletinfo()['birthtime'] + if self.options.descriptors: + # As blocks were generated every 10 min, the chain MTP timestamp is node_time - 60 min. + assert_equal(self.node_time - BLOCK_TIME * 6, wallet_birthtime) + else: + # No way of importing scripts/addresses with a custom time on a legacy wallet. + # It's always set to the beginning of time. + assert_equal(wallet_birthtime, 1) + # Rescan the wallet to detect the missing transaction wallet_watch_only.rescanblockchain() assert_equal(wallet_watch_only.gettransaction(tx_id)['confirmations'], 50) @@ -65,7 +77,16 @@ class WalletReindexTest(BitcoinTestFramework): # Verify the transaction is still 'confirmed' after reindex wallet_watch_only = node.get_wallet_rpc('watch_only') - assert_equal(wallet_watch_only.gettransaction(tx_id)['confirmations'], 50) + tx_info = wallet_watch_only.gettransaction(tx_id) + assert_equal(tx_info['confirmations'], 50) + + # Depending on the wallet type, the birth time changes. + if self.options.descriptors: + # For descriptors, verify the wallet updated the birth time to the transaction time + assert_equal(tx_info['time'], wallet_watch_only.getwalletinfo()['birthtime']) + else: + # For legacy, as the birth time was set to the beginning of time, verify it did not change + assert_equal(wallet_birthtime, 1) wallet_watch_only.unloadwallet()