Skip to content

Commit ed40fbb

Browse files
committed
Merge #15741: Batch write imported stuff in importmulti
0db94e5 wallet: Pass WalletBatch to CWallet::UnsetWalletFlag (João Barbosa) 6cb888b Apply the batch treatment to CWallet::SetAddressBook via ImportScriptPubKeys (Ben Woosley) 6154a09 Move some of ProcessImport into CWallet::Import* (Ben Woosley) ccb26cf Batch writes for importmulti (Andrew Chow) d6576e3 Have WalletBatch automatically flush every 1000 updates (Andrew Chow) 366fe0b Add AddWatchOnlyWithDB, AddKeyOriginWithDB, AddCScriptWithDB functions (Andrew Chow) Pull request description: Instead of writing each item to the wallet database individually, do them in batches so that the import runs faster. This was tested by importing a ranged descriptor for 10,000 keys. Current master ``` $ time src/bitcoin-cli -regtest -rpcwallet=importbig importmulti '[{"desc": "sh(wpkh([73111820/44h/1h/0h]tpubDDoT2SgEjaU5rerQpfcRDWPAcwyZ5g7xxHgVAfPwidgPDKVjm89d6jJ8AQotp35Np3m6VaysfUY1C2g68wFqUmraGbzhSsMF9YBuTGxpBaW/1/*))#3w7php47", "range": [0, 10000], "timestamp": "now", "internal": true, "keypool": false, "watchonly": true}]' ... real 7m45.29s ``` This PR: ``` $ time src/bitcoin-cli -regtest -rpcwallet=importbig4 importmulti '[{"desc": "pkh([73111820/44h/1h/0h]tpubDDoT2SgEjaU5rerQpfcRDWPAcwyZ5g7xxHgVAfPwidgPDKVjm89d6jJ8AQotp35Np3m6VaysfUY1C2g68wFqUmraGbzhSsMF9YBuTGxpBaW/1/*)#v65yjgmc", "range": [0, 10000], "timestamp": "now", "internal": true, "keypool": false, "watchonly": true}]' ... real 3.93s ``` Fixes #15739 ACKs for commit 0db94e: jb55: utACK 0db94e5 ariard: Tested ACK 0db94e5 Empact: re-utACK bitcoin/bitcoin@0db94e5 only change is re the privacy of `UnsetWalletFlagWithDB` and `AddCScriptWithDB`. Tree-SHA512: 3481308a64c99b6129f7bd328113dc291fe58743464628931feaebdef0e6ec770ddd5c19e4f9fbc1249a200acb04aaf62a8d914d53b0a29ac1e557576659c0cc
2 parents e78c331 + 0db94e5 commit ed40fbb

File tree

5 files changed

+163
-89
lines changed

5 files changed

+163
-89
lines changed

src/wallet/db.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -607,7 +607,9 @@ void BerkeleyBatch::Flush()
607607
if (fReadOnly)
608608
nMinutes = 1;
609609

610-
env->dbenv->txn_checkpoint(nMinutes ? gArgs.GetArg("-dblogsize", DEFAULT_WALLET_DBLOGSIZE) * 1024 : 0, nMinutes, 0);
610+
if (env) { // env is nullptr for dummy databases (i.e. in tests). Don't actually flush if env is nullptr so we don't segfault
611+
env->dbenv->txn_checkpoint(nMinutes ? gArgs.GetArg("-dblogsize", DEFAULT_WALLET_DBLOGSIZE) * 1024 : 0, nMinutes, 0);
612+
}
611613
}
612614

613615
void BerkeleyDatabase::IncrementUpdateCounter()

src/wallet/rpcdump.cpp

Lines changed: 8 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1273,55 +1273,17 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con
12731273

12741274
// All good, time to import
12751275
pwallet->MarkDirty();
1276-
for (const auto& entry : import_data.import_scripts) {
1277-
if (!pwallet->HaveCScript(CScriptID(entry)) && !pwallet->AddCScript(entry)) {
1278-
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding script to wallet");
1279-
}
1280-
}
1281-
for (const auto& entry : privkey_map) {
1282-
const CKey& key = entry.second;
1283-
CPubKey pubkey = key.GetPubKey();
1284-
const CKeyID& id = entry.first;
1285-
assert(key.VerifyPubKey(pubkey));
1286-
pwallet->mapKeyMetadata[id].nCreateTime = timestamp;
1287-
// If the private key is not present in the wallet, insert it.
1288-
if (!pwallet->HaveKey(id) && !pwallet->AddKeyPubKey(key, pubkey)) {
1289-
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
1290-
}
1291-
pwallet->UpdateTimeFirstKey(timestamp);
1276+
if (!pwallet->ImportScripts(import_data.import_scripts)) {
1277+
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding script to wallet");
12921278
}
1293-
for (const auto& entry : import_data.key_origins) {
1294-
pwallet->AddKeyOrigin(entry.second.first, entry.second.second);
1279+
if (!pwallet->ImportPrivKeys(privkey_map, timestamp)) {
1280+
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
12951281
}
1296-
for (const CKeyID& id : ordered_pubkeys) {
1297-
auto entry = pubkey_map.find(id);
1298-
if (entry == pubkey_map.end()) {
1299-
continue;
1300-
}
1301-
const CPubKey& pubkey = entry->second;
1302-
CPubKey temp;
1303-
if (!pwallet->GetPubKey(id, temp) && !pwallet->AddWatchOnly(GetScriptForRawPubKey(pubkey), timestamp)) {
1304-
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
1305-
}
1306-
pwallet->mapKeyMetadata[id].nCreateTime = timestamp;
1307-
1308-
// Add to keypool only works with pubkeys
1309-
if (add_keypool) {
1310-
pwallet->AddKeypoolPubkey(pubkey, internal);
1311-
}
1282+
if (!pwallet->ImportPubKeys(ordered_pubkeys, pubkey_map, import_data.key_origins, add_keypool, internal, timestamp)) {
1283+
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
13121284
}
1313-
1314-
for (const CScript& script : script_pub_keys) {
1315-
if (!have_solving_data || !::IsMine(*pwallet, script)) { // Always call AddWatchOnly for non-solvable watch-only, so that watch timestamp gets updated
1316-
if (!pwallet->AddWatchOnly(script, timestamp)) {
1317-
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
1318-
}
1319-
}
1320-
CTxDestination dest;
1321-
ExtractDestination(script, dest);
1322-
if (!internal && IsValidDestination(dest)) {
1323-
pwallet->SetAddressBook(dest, label, "receive");
1324-
}
1285+
if (!pwallet->ImportScriptPubKeys(label, script_pub_keys, have_solving_data, internal, timestamp)) {
1286+
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
13251287
}
13261288

13271289
result.pushKV("success", UniValue(true));

src/wallet/wallet.cpp

Lines changed: 116 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ bool CWallet::AddKeyPubKeyWithDB(WalletBatch& batch, const CKey& secret, const C
320320
secret.GetPrivKey(),
321321
mapKeyMetadata[pubkey.GetID()]);
322322
}
323-
UnsetWalletFlag(WALLET_FLAG_BLANK_WALLET);
323+
UnsetWalletFlagWithDB(batch, WALLET_FLAG_BLANK_WALLET);
324324
return true;
325325
}
326326

@@ -362,12 +362,6 @@ void CWallet::LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata&
362362
m_script_metadata[script_id] = meta;
363363
}
364364

365-
// Writes a keymetadata for a public key. overwrite specifies whether to overwrite an existing metadata for that key if there exists one.
366-
bool CWallet::WriteKeyMetadata(const CKeyMetadata& meta, const CPubKey& pubkey, const bool overwrite)
367-
{
368-
return WalletBatch(*database).WriteKeyMetadata(meta, pubkey, overwrite);
369-
}
370-
371365
void CWallet::UpgradeKeyMetadata()
372366
{
373367
AssertLockHeld(cs_wallet);
@@ -376,7 +370,6 @@ void CWallet::UpgradeKeyMetadata()
376370
}
377371

378372
std::unique_ptr<WalletBatch> batch = MakeUnique<WalletBatch>(*database);
379-
size_t cnt = 0;
380373
for (auto& meta_pair : mapKeyMetadata) {
381374
CKeyMetadata& meta = meta_pair.second;
382375
if (!meta.hd_seed_id.IsNull() && !meta.has_key_origin && meta.hdKeypath != "s") { // If the hdKeypath is "s", that's the seed and it doesn't have a key origin
@@ -399,10 +392,6 @@ void CWallet::UpgradeKeyMetadata()
399392
CPubKey pubkey;
400393
if (GetPubKey(meta_pair.first, pubkey)) {
401394
batch->WriteKeyMetadata(meta, pubkey, true);
402-
if (++cnt % 1000 == 0) {
403-
// avoid creating overlarge in-memory batches in case the wallet contains large amounts of keys
404-
batch.reset(new WalletBatch(*database));
405-
}
406395
}
407396
}
408397
}
@@ -432,11 +421,17 @@ void CWallet::UpdateTimeFirstKey(int64_t nCreateTime)
432421
}
433422

434423
bool CWallet::AddCScript(const CScript& redeemScript)
424+
{
425+
WalletBatch batch(*database);
426+
return AddCScriptWithDB(batch, redeemScript);
427+
}
428+
429+
bool CWallet::AddCScriptWithDB(WalletBatch& batch, const CScript& redeemScript)
435430
{
436431
if (!CCryptoKeyStore::AddCScript(redeemScript))
437432
return false;
438-
if (WalletBatch(*database).WriteCScript(Hash160(redeemScript), redeemScript)) {
439-
UnsetWalletFlag(WALLET_FLAG_BLANK_WALLET);
433+
if (batch.WriteCScript(Hash160(redeemScript), redeemScript)) {
434+
UnsetWalletFlagWithDB(batch, WALLET_FLAG_BLANK_WALLET);
440435
return true;
441436
}
442437
return false;
@@ -457,20 +452,32 @@ bool CWallet::LoadCScript(const CScript& redeemScript)
457452
return CCryptoKeyStore::AddCScript(redeemScript);
458453
}
459454

460-
bool CWallet::AddWatchOnly(const CScript& dest)
455+
bool CWallet::AddWatchOnlyWithDB(WalletBatch &batch, const CScript& dest)
461456
{
462457
if (!CCryptoKeyStore::AddWatchOnly(dest))
463458
return false;
464459
const CKeyMetadata& meta = m_script_metadata[CScriptID(dest)];
465460
UpdateTimeFirstKey(meta.nCreateTime);
466461
NotifyWatchonlyChanged(true);
467-
if (WalletBatch(*database).WriteWatchOnly(dest, meta)) {
468-
UnsetWalletFlag(WALLET_FLAG_BLANK_WALLET);
462+
if (batch.WriteWatchOnly(dest, meta)) {
463+
UnsetWalletFlagWithDB(batch, WALLET_FLAG_BLANK_WALLET);
469464
return true;
470465
}
471466
return false;
472467
}
473468

469+
bool CWallet::AddWatchOnlyWithDB(WalletBatch &batch, const CScript& dest, int64_t create_time)
470+
{
471+
m_script_metadata[CScriptID(dest)].nCreateTime = create_time;
472+
return AddWatchOnlyWithDB(batch, dest);
473+
}
474+
475+
bool CWallet::AddWatchOnly(const CScript& dest)
476+
{
477+
WalletBatch batch(*database);
478+
return AddWatchOnlyWithDB(batch, dest);
479+
}
480+
474481
bool CWallet::AddWatchOnly(const CScript& dest, int64_t nCreateTime)
475482
{
476483
m_script_metadata[CScriptID(dest)].nCreateTime = nCreateTime;
@@ -1542,10 +1549,16 @@ void CWallet::SetWalletFlag(uint64_t flags)
15421549
}
15431550

15441551
void CWallet::UnsetWalletFlag(uint64_t flag)
1552+
{
1553+
WalletBatch batch(*database);
1554+
UnsetWalletFlagWithDB(batch, flag);
1555+
}
1556+
1557+
void CWallet::UnsetWalletFlagWithDB(WalletBatch& batch, uint64_t flag)
15451558
{
15461559
LOCK(cs_wallet);
15471560
m_wallet_flags &= ~flag;
1548-
if (!WalletBatch(*database).WriteWalletFlags(m_wallet_flags))
1561+
if (!batch.WriteWalletFlags(m_wallet_flags))
15491562
throw std::runtime_error(std::string(__func__) + ": writing wallet flags failed");
15501563
}
15511564

@@ -1606,6 +1619,80 @@ bool CWallet::DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut>
16061619
return true;
16071620
}
16081621

1622+
bool CWallet::ImportScripts(const std::set<CScript> scripts)
1623+
{
1624+
WalletBatch batch(*database);
1625+
for (const auto& entry : scripts) {
1626+
if (!HaveCScript(CScriptID(entry)) && !AddCScriptWithDB(batch, entry)) {
1627+
return false;
1628+
}
1629+
}
1630+
return true;
1631+
}
1632+
1633+
bool CWallet::ImportPrivKeys(const std::map<CKeyID, CKey>& privkey_map, const int64_t timestamp)
1634+
{
1635+
WalletBatch batch(*database);
1636+
for (const auto& entry : privkey_map) {
1637+
const CKey& key = entry.second;
1638+
CPubKey pubkey = key.GetPubKey();
1639+
const CKeyID& id = entry.first;
1640+
assert(key.VerifyPubKey(pubkey));
1641+
mapKeyMetadata[id].nCreateTime = timestamp;
1642+
// If the private key is not present in the wallet, insert it.
1643+
if (!HaveKey(id) && !AddKeyPubKeyWithDB(batch, key, pubkey)) {
1644+
return false;
1645+
}
1646+
UpdateTimeFirstKey(timestamp);
1647+
}
1648+
return true;
1649+
}
1650+
1651+
bool CWallet::ImportPubKeys(const std::vector<CKeyID>& ordered_pubkeys, const std::map<CKeyID, CPubKey>& pubkey_map, const std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>>& key_origins, const bool add_keypool, const bool internal, const int64_t timestamp)
1652+
{
1653+
WalletBatch batch(*database);
1654+
for (const auto& entry : key_origins) {
1655+
AddKeyOriginWithDB(batch, entry.second.first, entry.second.second);
1656+
}
1657+
for (const CKeyID& id : ordered_pubkeys) {
1658+
auto entry = pubkey_map.find(id);
1659+
if (entry == pubkey_map.end()) {
1660+
continue;
1661+
}
1662+
const CPubKey& pubkey = entry->second;
1663+
CPubKey temp;
1664+
if (!GetPubKey(id, temp) && !AddWatchOnlyWithDB(batch, GetScriptForRawPubKey(pubkey), timestamp)) {
1665+
return false;
1666+
}
1667+
mapKeyMetadata[id].nCreateTime = timestamp;
1668+
1669+
// Add to keypool only works with pubkeys
1670+
if (add_keypool) {
1671+
AddKeypoolPubkeyWithDB(pubkey, internal, batch);
1672+
NotifyCanGetAddressesChanged();
1673+
}
1674+
}
1675+
return true;
1676+
}
1677+
1678+
bool CWallet::ImportScriptPubKeys(const std::string& label, const std::set<CScript>& script_pub_keys, const bool have_solving_data, const bool internal, const int64_t timestamp)
1679+
{
1680+
WalletBatch batch(*database);
1681+
for (const CScript& script : script_pub_keys) {
1682+
if (!have_solving_data || !::IsMine(*this, script)) { // Always call AddWatchOnly for non-solvable watch-only, so that watch timestamp gets updated
1683+
if (!AddWatchOnlyWithDB(batch, script, timestamp)) {
1684+
return false;
1685+
}
1686+
}
1687+
CTxDestination dest;
1688+
ExtractDestination(script, dest);
1689+
if (!internal && IsValidDestination(dest)) {
1690+
SetAddressBookWithDB(batch, dest, label, "receive");
1691+
}
1692+
}
1693+
return true;
1694+
}
1695+
16091696
int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, bool use_max_sig)
16101697
{
16111698
std::vector<CTxOut> txouts;
@@ -3149,8 +3236,7 @@ DBErrors CWallet::ZapWalletTx(std::vector<CWalletTx>& vWtx)
31493236
return DBErrors::LOAD_OK;
31503237
}
31513238

3152-
3153-
bool CWallet::SetAddressBook(const CTxDestination& address, const std::string& strName, const std::string& strPurpose)
3239+
bool CWallet::SetAddressBookWithDB(WalletBatch& batch, const CTxDestination& address, const std::string& strName, const std::string& strPurpose)
31543240
{
31553241
bool fUpdated = false;
31563242
{
@@ -3163,9 +3249,15 @@ bool CWallet::SetAddressBook(const CTxDestination& address, const std::string& s
31633249
}
31643250
NotifyAddressBookChanged(this, address, strName, ::IsMine(*this, address) != ISMINE_NO,
31653251
strPurpose, (fUpdated ? CT_UPDATED : CT_NEW) );
3166-
if (!strPurpose.empty() && !WalletBatch(*database).WritePurpose(EncodeDestination(address), strPurpose))
3252+
if (!strPurpose.empty() && !batch.WritePurpose(EncodeDestination(address), strPurpose))
31673253
return false;
3168-
return WalletBatch(*database).WriteName(EncodeDestination(address), strName);
3254+
return batch.WriteName(EncodeDestination(address), strName);
3255+
}
3256+
3257+
bool CWallet::SetAddressBook(const CTxDestination& address, const std::string& strName, const std::string& strPurpose)
3258+
{
3259+
WalletBatch batch(*database);
3260+
return SetAddressBookWithDB(batch, address, strName, strPurpose);
31693261
}
31703262

31713263
bool CWallet::DelAddressBook(const CTxDestination& address)
@@ -3315,13 +3407,6 @@ bool CWallet::TopUpKeyPool(unsigned int kpSize)
33153407
return true;
33163408
}
33173409

3318-
void CWallet::AddKeypoolPubkey(const CPubKey& pubkey, const bool internal)
3319-
{
3320-
WalletBatch batch(*database);
3321-
AddKeypoolPubkeyWithDB(pubkey, internal, batch);
3322-
NotifyCanGetAddressesChanged();
3323-
}
3324-
33253410
void CWallet::AddKeypoolPubkeyWithDB(const CPubKey& pubkey, const bool internal, WalletBatch& batch)
33263411
{
33273412
LOCK(cs_wallet);
@@ -4443,12 +4528,12 @@ bool CWallet::GetKeyOrigin(const CKeyID& keyID, KeyOriginInfo& info) const
44434528
return true;
44444529
}
44454530

4446-
bool CWallet::AddKeyOrigin(const CPubKey& pubkey, const KeyOriginInfo& info)
4531+
bool CWallet::AddKeyOriginWithDB(WalletBatch& batch, const CPubKey& pubkey, const KeyOriginInfo& info)
44474532
{
44484533
LOCK(cs_wallet);
44494534
std::copy(info.fingerprint, info.fingerprint + 4, mapKeyMetadata[pubkey.GetID()].key_origin.fingerprint);
44504535
mapKeyMetadata[pubkey.GetID()].key_origin.path = info.path;
44514536
mapKeyMetadata[pubkey.GetID()].has_key_origin = true;
44524537
mapKeyMetadata[pubkey.GetID()].hdKeypath = WriteHDKeypath(info.path);
4453-
return WriteKeyMetadata(mapKeyMetadata[pubkey.GetID()], pubkey, true);
4538+
return batch.WriteKeyMetadata(mapKeyMetadata[pubkey.GetID()], pubkey, true);
44544539
}

0 commit comments

Comments
 (0)