Skip to content

Commit 4aaa3b5

Browse files
committed
Merge bitcoin/bitcoin#25351: rpc, wallet: Scan mempool after import* - Second attempt
1be7964 test, wallet: Add mempool rescan test for import RPCs (Fabian Jahr) 833ce76 rpc, wallet: Document mempool rescan after importdescriptor, importwallet (Fabian Jahr) 0e396d1 rpc, wallet: Document mempool scan after importmulti (Fabian Jahr) e6d3ef8 rpc, wallet: Document mempool scan after importpubkey (Fabian Jahr) 6d3db52 rpc, wallet: Document and test mempool scan after importprivkey (João Barbosa) 3abdbbb rpc, wallet: Document and test mempool scan after importaddress (João Barbosa) 236239b wallet: Rescan mempool for transactions as well (Fabian Jahr) Pull request description: This PR picks up the work from #18964 and closes #18954. It should incorporate all the unaddressed feedback from the PR: - Mempool rescan now expanded to all relevant import* RPCs - Added documentation in the help of each RPC - More tests ACKs for top commit: Sjors: re-utACK 1be7964 (only a test change) achow101: ACK 1be7964 w0xlt: reACK bitcoin/bitcoin@1be7964 Tree-SHA512: b62fed5f97c6c242b2af417b41c9696a1f18878483d9e1c9429791f9c05257f57a00540a9a84df23c49faf6a61c3109c22972de81540083f38b506217804fcc5
2 parents f002f8a + 1be7964 commit 4aaa3b5

File tree

6 files changed

+99
-17
lines changed

6 files changed

+99
-17
lines changed

src/wallet/rpc/backup.cpp

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,13 @@ RPCHelpMan importprivkey()
100100
"Hint: use importmulti to import more than one private key.\n"
101101
"\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n"
102102
"may report that the imported key exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n"
103+
"The rescan parameter can be set to false if the key was never used to create transactions. If it is set to false,\n"
104+
"but the key was used to create transactions, rescanwallet needs to be called with the appropriate block range.\n"
103105
"Note: Use \"getwalletinfo\" to query the scanning progress.\n",
104106
{
105107
{"privkey", RPCArg::Type::STR, RPCArg::Optional::NO, "The private key (see dumpprivkey)"},
106108
{"label", RPCArg::Type::STR, RPCArg::DefaultHint{"current label if address exists, otherwise \"\""}, "An optional label"},
107-
{"rescan", RPCArg::Type::BOOL, RPCArg::Default{true}, "Rescan the wallet for transactions"},
109+
{"rescan", RPCArg::Type::BOOL, RPCArg::Default{true}, "Scan the chain and mempool for wallet transactions."},
108110
},
109111
RPCResult{RPCResult::Type::NONE, "", ""},
110112
RPCExamples{
@@ -201,6 +203,8 @@ RPCHelpMan importaddress()
201203
"\nAdds an address or script (in hex) that can be watched as if it were in your wallet but cannot be used to spend. Requires a new wallet backup.\n"
202204
"\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n"
203205
"may report that the imported address exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n"
206+
"The rescan parameter can be set to false if the key was never used to create transactions. If it is set to false,\n"
207+
"but the key was used to create transactions, rescanwallet needs to be called with the appropriate block range.\n"
204208
"If you have the full public key, you should call importpubkey instead of this.\n"
205209
"Hint: use importmulti to import more than one address.\n"
206210
"\nNote: If you import a non-standard raw script in hex form, outputs sending to it will be treated\n"
@@ -210,7 +214,7 @@ RPCHelpMan importaddress()
210214
{
211215
{"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The Bitcoin address (or hex-encoded script)"},
212216
{"label", RPCArg::Type::STR, RPCArg::Default{""}, "An optional label"},
213-
{"rescan", RPCArg::Type::BOOL, RPCArg::Default{true}, "Rescan the wallet for transactions"},
217+
{"rescan", RPCArg::Type::BOOL, RPCArg::Default{true}, "Scan the chain and mempool for wallet transactions."},
214218
{"p2sh", RPCArg::Type::BOOL, RPCArg::Default{false}, "Add the P2SH version of the script as well"},
215219
},
216220
RPCResult{RPCResult::Type::NONE, "", ""},
@@ -401,11 +405,13 @@ RPCHelpMan importpubkey()
401405
"Hint: use importmulti to import more than one public key.\n"
402406
"\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n"
403407
"may report that the imported pubkey exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n"
408+
"The rescan parameter can be set to false if the key was never used to create transactions. If it is set to false,\n"
409+
"but the key was used to create transactions, rescanwallet needs to be called with the appropriate block range.\n"
404410
"Note: Use \"getwalletinfo\" to query the scanning progress.\n",
405411
{
406412
{"pubkey", RPCArg::Type::STR, RPCArg::Optional::NO, "The hex-encoded public key"},
407413
{"label", RPCArg::Type::STR, RPCArg::Default{""}, "An optional label"},
408-
{"rescan", RPCArg::Type::BOOL, RPCArg::Default{true}, "Rescan the wallet for transactions"},
414+
{"rescan", RPCArg::Type::BOOL, RPCArg::Default{true}, "Scan the chain and mempool for wallet transactions."},
409415
},
410416
RPCResult{RPCResult::Type::NONE, "", ""},
411417
RPCExamples{
@@ -484,7 +490,7 @@ RPCHelpMan importwallet()
484490
{
485491
return RPCHelpMan{"importwallet",
486492
"\nImports keys from a wallet dump file (see dumpwallet). Requires a new wallet backup to include imported keys.\n"
487-
"Note: Use \"getwalletinfo\" to query the scanning progress.\n",
493+
"Note: Blockchain and Mempool will be rescanned after a successful import. Use \"getwalletinfo\" to query the scanning progress.\n",
488494
{
489495
{"filename", RPCArg::Type::STR, RPCArg::Optional::NO, "The wallet file"},
490496
},
@@ -1250,6 +1256,8 @@ RPCHelpMan importmulti()
12501256
"Conversely, if all the private keys are provided and the address/script is spendable, the watchonly option must be set to false, or a warning will be returned.\n"
12511257
"\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n"
12521258
"may report that the imported keys, addresses or scripts exist but related transactions are still missing.\n"
1259+
"The rescan parameter can be set to false if the key was never used to create transactions. If it is set to false,\n"
1260+
"but the key was used to create transactions, rescanwallet needs to be called with the appropriate block range.\n"
12531261
"Note: Use \"getwalletinfo\" to query the scanning progress.\n",
12541262
{
12551263
{"requests", RPCArg::Type::ARR, RPCArg::Optional::NO, "Data to be imported",
@@ -1291,7 +1299,7 @@ RPCHelpMan importmulti()
12911299
"\"requests\""},
12921300
{"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "",
12931301
{
1294-
{"rescan", RPCArg::Type::BOOL, RPCArg::Default{true}, "Stating if should rescan the blockchain after all imports"},
1302+
{"rescan", RPCArg::Type::BOOL, RPCArg::Default{true}, "Scan the chain and mempool for wallet transactions after all imports."},
12951303
},
12961304
"\"options\""},
12971305
},
@@ -1593,7 +1601,7 @@ RPCHelpMan importdescriptors()
15931601
" Use the string \"now\" to substitute the current synced blockchain time.\n"
15941602
" \"now\" can be specified to bypass scanning, for outputs which are known to never have been used, and\n"
15951603
" 0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest timestamp\n"
1596-
" of all descriptors being imported will be scanned.",
1604+
"of all descriptors being imported will be scanned as well as the mempool.",
15971605
/*oneline_description=*/"", {"timestamp | \"now\"", "integer / string"}
15981606
},
15991607
{"internal", RPCArg::Type::BOOL, RPCArg::Default{false}, "Whether matching outputs should be treated as not incoming payments (e.g. change)"},

src/wallet/test/wallet_tests.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,6 @@ static const std::shared_ptr<CWallet> TestLoadWallet(WalletContext& context)
5353
auto database = MakeWalletDatabase("", options, status, error);
5454
auto wallet = CWallet::Create(context, "", std::move(database), options.create_flags, error, warnings);
5555
NotifyWalletLoaded(context, wallet);
56-
if (context.chain) {
57-
wallet->postInitProcess();
58-
}
5956
return wallet;
6057
}
6158

@@ -768,6 +765,7 @@ BOOST_FIXTURE_TEST_CASE(CreateWallet, TestChain100Setup)
768765
// being blocked
769766
wallet = TestLoadWallet(context);
770767
BOOST_CHECK(rescan_completed);
768+
// AddToWallet events for block_tx and mempool_tx
771769
BOOST_CHECK_EQUAL(addtx_count, 2);
772770
{
773771
LOCK(wallet->cs_wallet);
@@ -780,6 +778,8 @@ BOOST_FIXTURE_TEST_CASE(CreateWallet, TestChain100Setup)
780778
// transactionAddedToMempool events are processed
781779
promise.set_value();
782780
SyncWithValidationInterfaceQueue();
781+
// AddToWallet events for block_tx and mempool_tx events are counted a
782+
// second time as the notificaiton queue is processed
783783
BOOST_CHECK_EQUAL(addtx_count, 4);
784784

785785

@@ -803,7 +803,7 @@ BOOST_FIXTURE_TEST_CASE(CreateWallet, TestChain100Setup)
803803
SyncWithValidationInterfaceQueue();
804804
});
805805
wallet = TestLoadWallet(context);
806-
BOOST_CHECK_EQUAL(addtx_count, 4);
806+
BOOST_CHECK_EQUAL(addtx_count, 2);
807807
{
808808
LOCK(wallet->cs_wallet);
809809
BOOST_CHECK_EQUAL(wallet->mapWallet.count(block_tx.GetHash()), 1U);

src/wallet/wallet.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1678,7 +1678,8 @@ int64_t CWallet::RescanFromTime(int64_t startTime, const WalletRescanReserver& r
16781678
/**
16791679
* Scan the block chain (starting in start_block) for transactions
16801680
* from or to us. If fUpdate is true, found transactions that already
1681-
* exist in the wallet will be updated.
1681+
* exist in the wallet will be updated. If max_height is not set, the
1682+
* mempool will be scanned as well.
16821683
*
16831684
* @param[in] start_block Scan starting block. If block is not on the active
16841685
* chain, the scan will return SUCCESS immediately.
@@ -1799,6 +1800,10 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc
17991800
}
18001801
}
18011802
}
1803+
if (!max_height) {
1804+
WalletLogPrintf("Scanning current mempool transactions.\n");
1805+
WITH_LOCK(cs_wallet, chain().requestMempoolTransactions(*this));
1806+
}
18021807
ShowProgress(strprintf("%s " + _("Rescanning…").translated, GetDisplayName()), 100); // hide progress dialog in GUI
18031808
if (block_height && fAbortRescan) {
18041809
WalletLogPrintf("Rescan aborted at block %d. Progress=%f\n", block_height, progress_current);

test/functional/wallet_balance.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,26 @@ def test_balances(*, fee_node_1=0):
273273
self.generatetoaddress(self.nodes[1], 1, ADDRESS_WATCHONLY)
274274
assert_equal(self.nodes[0].getbalance(minconf=0), total_amount + 1) # The reorg recovered our fee of 1 coin
275275

276+
if not self.options.descriptors:
277+
self.log.info('Check if mempool is taken into account after import*')
278+
address = self.nodes[0].getnewaddress()
279+
privkey = self.nodes[0].dumpprivkey(address)
280+
self.nodes[0].sendtoaddress(address, 0.1)
281+
self.nodes[0].unloadwallet('')
282+
# check importaddress on fresh wallet
283+
self.nodes[0].createwallet('w1', False, True)
284+
self.nodes[0].importaddress(address)
285+
assert_equal(self.nodes[0].getbalances()['mine']['untrusted_pending'], 0)
286+
assert_equal(self.nodes[0].getbalances()['watchonly']['untrusted_pending'], Decimal('0.1'))
287+
self.nodes[0].importprivkey(privkey)
288+
assert_equal(self.nodes[0].getbalances()['mine']['untrusted_pending'], Decimal('0.1'))
289+
assert_equal(self.nodes[0].getbalances()['watchonly']['untrusted_pending'], 0)
290+
self.nodes[0].unloadwallet('w1')
291+
# check importprivkey on fresh wallet
292+
self.nodes[0].createwallet('w2', False, True)
293+
self.nodes[0].importprivkey(privkey)
294+
assert_equal(self.nodes[0].getbalances()['mine']['untrusted_pending'], Decimal('0.1'))
295+
276296

277297
if __name__ == '__main__':
278298
WalletTest().main()

test/functional/wallet_import_rescan.py

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ def check(self, txid=None, amount=None, confirmation_height=None):
8787
assert_equal(len(txs), self.expected_txs)
8888

8989
addresses = self.node.listreceivedbyaddress(minconf=0, include_watchonly=True, address_filter=self.address['address'])
90+
9091
if self.expected_txs:
9192
assert_equal(len(addresses[0]["txids"]), self.expected_txs)
9293

@@ -98,13 +99,18 @@ def check(self, txid=None, amount=None, confirmation_height=None):
9899
assert_equal(tx["category"], "receive")
99100
assert_equal(tx["label"], self.label)
100101
assert_equal(tx["txid"], txid)
101-
assert_equal(tx["confirmations"], 1 + current_height - confirmation_height)
102-
assert "trusted" not in tx
102+
103+
# If no confirmation height is given, the tx is still in the
104+
# mempool.
105+
confirmations = (1 + current_height - confirmation_height) if confirmation_height else 0
106+
assert_equal(tx["confirmations"], confirmations)
107+
if confirmations:
108+
assert "trusted" not in tx
103109

104110
address, = [ad for ad in addresses if txid in ad["txids"]]
105111
assert_equal(address["address"], self.address["address"])
106112
assert_equal(address["amount"], self.expected_balance)
107-
assert_equal(address["confirmations"], 1 + current_height - confirmation_height)
113+
assert_equal(address["confirmations"], confirmations)
108114
# Verify the transaction is correctly marked watchonly depending on
109115
# whether the transaction pays to an imported public key or
110116
# imported private key. The test setup ensures that transaction
@@ -162,11 +168,12 @@ def setup_network(self):
162168
self.import_deterministic_coinbase_privkeys()
163169
self.stop_nodes()
164170

165-
self.start_nodes()
171+
self.start_nodes(extra_args=[["[email protected]"]] * self.num_nodes)
166172
for i in range(1, self.num_nodes):
167173
self.connect_nodes(i, 0)
168174

169175
def run_test(self):
176+
170177
# Create one transaction on node 0 with a unique amount for
171178
# each possible type of wallet import RPC.
172179
for i, variant in enumerate(IMPORT_VARIANTS):
@@ -207,7 +214,7 @@ def run_test(self):
207214
variant.check()
208215

209216
# Create new transactions sending to each address.
210-
for i, variant in enumerate(IMPORT_VARIANTS):
217+
for variant in IMPORT_VARIANTS:
211218
variant.sent_amount = get_rand_amount()
212219
variant.sent_txid = self.nodes[0].sendtoaddress(variant.address["address"], variant.sent_amount)
213220
self.generate(self.nodes[0], 1) # Generate one block for each send
@@ -223,6 +230,46 @@ def run_test(self):
223230
variant.expected_txs += 1
224231
variant.check(variant.sent_txid, variant.sent_amount, variant.confirmation_height)
225232

233+
self.log.info('Test that the mempool is rescanned as well if the rescan parameter is set to true')
234+
235+
# The late timestamp and pruned variants are not necessary when testing mempool rescan
236+
mempool_variants = [variant for variant in IMPORT_VARIANTS if variant.rescan != Rescan.late_timestamp and not variant.prune]
237+
# No further blocks are mined so the timestamp will stay the same
238+
timestamp = self.nodes[0].getblockheader(self.nodes[0].getbestblockhash())["time"]
239+
240+
# Create one transaction on node 0 with a unique amount for
241+
# each possible type of wallet import RPC.
242+
for i, variant in enumerate(mempool_variants):
243+
variant.label = "mempool label {} {}".format(i, variant)
244+
variant.address = self.nodes[1].getaddressinfo(self.nodes[1].getnewaddress(
245+
label=variant.label,
246+
address_type=variant.address_type.value,
247+
))
248+
variant.key = self.nodes[1].dumpprivkey(variant.address["address"])
249+
variant.initial_amount = get_rand_amount()
250+
variant.initial_txid = self.nodes[0].sendtoaddress(variant.address["address"], variant.initial_amount)
251+
variant.confirmation_height = 0
252+
variant.timestamp = timestamp
253+
254+
assert_equal(len(self.nodes[0].getrawmempool()), len(mempool_variants))
255+
self.sync_mempools()
256+
257+
# For each variation of wallet key import, invoke the import RPC and
258+
# check the results from getbalance and listtransactions.
259+
for variant in mempool_variants:
260+
self.log.info('Run import for mempool variant {}'.format(variant))
261+
expect_rescan = variant.rescan == Rescan.yes
262+
variant.node = self.nodes[2 + IMPORT_NODES.index(ImportNode(variant.prune, expect_rescan))]
263+
variant.do_import(variant.timestamp)
264+
if expect_rescan:
265+
variant.expected_balance = variant.initial_amount
266+
variant.expected_txs = 1
267+
variant.check(variant.initial_txid, variant.initial_amount)
268+
else:
269+
variant.expected_balance = 0
270+
variant.expected_txs = 0
271+
variant.check()
272+
226273

227274
if __name__ == "__main__":
228275
ImportRescanTest().main()

test/functional/wallet_importdescriptors.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -480,7 +480,9 @@ def run_test(self):
480480
addr = wmulti_pub.getnewaddress('', 'bech32')
481481
assert_equal(addr, 'bcrt1qp8s25ckjl7gr6x2q3dx3tn2pytwp05upkjztk6ey857tt50r5aeqn6mvr9') # Derived at m/84'/0'/0'/1
482482
change_addr = wmulti_pub.getrawchangeaddress('bech32')
483-
assert_equal(change_addr, 'bcrt1qt9uhe3a9hnq7vajl7a094z4s3crm9ttf8zw3f5v9gr2nyd7e3lnsy44n8e')
483+
assert_equal(change_addr, 'bcrt1qzxl0qz2t88kljdnkzg4n4gapr6kte26390gttrg79x66nt4p04fssj53nl')
484+
assert(send_txid in self.nodes[0].getrawmempool(True))
485+
assert(send_txid in (x['txid'] for x in wmulti_pub.listunspent(0)))
484486
assert_equal(wmulti_pub.getwalletinfo()['keypoolsize'], 999)
485487

486488
# generate some utxos for next tests

0 commit comments

Comments
 (0)