Skip to content

Commit a7f4f1a

Browse files
committed
Merge bitcoin#28894: wallet: batch all individual spkms setup db writes in a single db txn
f053024 wallet: batch external signer descriptor import (Sjors Provoost) 1f65241 wallet: descriptors setup, batch db operations (furszy) 3eb769f wallet: batch legacy spkm TopUp (furszy) 075aa44 wallet: batch descriptor spkm TopUp (furszy) bb4554c bench: add benchmark for wallet creation procedure (furszy) Pull request description: Work decoupled from bitcoin#28574. Instead of performing multiple single write operations per spkm setup call, this PR batches them all within a single atomic db txn. Speeding up the process and preventing the wallet from entering an inconsistent state if any of the intermediate transactions fail (which shouldn't happen but.. if it does, it is better to not store any spkm rather than storing them partially). To compare the changes, added benchmark in the first commit. ACKs for top commit: Sjors: re-utACK f053024 achow101: ACK f053024 BrandonOdiwuor: ACK f053024 theStack: Code-review ACK f053024 Tree-SHA512: aead8548473e17d4d53e8e7039bbaf5e8bf2fe83f33b33f81cdedefe8a31b7003ceb6d5379b1bad1ca2692e909492009a21284ec8338eede078df3d19046ab5a
2 parents 14d1732 + f053024 commit a7f4f1a

8 files changed

+111
-26
lines changed

src/Makefile.bench.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ endif
8484
if ENABLE_WALLET
8585
bench_bench_bitcoin_SOURCES += bench/coin_selection.cpp
8686
bench_bench_bitcoin_SOURCES += bench/wallet_balance.cpp
87+
bench_bench_bitcoin_SOURCES += bench/wallet_create.cpp
8788
bench_bench_bitcoin_SOURCES += bench/wallet_loading.cpp
8889
bench_bench_bitcoin_SOURCES += bench/wallet_create_tx.cpp
8990
bench_bench_bitcoin_LDADD += $(BDB_LIBS) $(SQLITE_LIBS)

src/bench/wallet_create.cpp

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright (c) 2023-present The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or https://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <bench/bench.h>
6+
#include <node/context.h>
7+
#include <random.h>
8+
#include <test/util/setup_common.h>
9+
#include <wallet/context.h>
10+
#include <wallet/wallet.h>
11+
12+
namespace wallet {
13+
static void WalletCreate(benchmark::Bench& bench, bool encrypted)
14+
{
15+
auto test_setup = MakeNoLogFileContext<TestingSetup>();
16+
FastRandomContext random;
17+
18+
WalletContext context;
19+
context.args = &test_setup->m_args;
20+
context.chain = test_setup->m_node.chain.get();
21+
22+
DatabaseOptions options;
23+
options.require_format = DatabaseFormat::SQLITE;
24+
options.require_create = true;
25+
options.create_flags = WALLET_FLAG_DESCRIPTORS;
26+
27+
if (encrypted) {
28+
options.create_passphrase = random.rand256().ToString();
29+
}
30+
31+
DatabaseStatus status;
32+
bilingual_str error_string;
33+
std::vector<bilingual_str> warnings;
34+
35+
fs::path wallet_path = test_setup->m_path_root / strprintf("test_wallet_%d", random.rand32()).c_str();
36+
bench.run([&] {
37+
auto wallet = CreateWallet(context, wallet_path.u8string(), /*load_on_start=*/std::nullopt, options, status, error_string, warnings);
38+
assert(status == DatabaseStatus::SUCCESS);
39+
assert(wallet != nullptr);
40+
41+
// Cleanup
42+
wallet.reset();
43+
fs::remove_all(wallet_path);
44+
});
45+
}
46+
47+
static void WalletCreatePlain(benchmark::Bench& bench) { WalletCreate(bench, /*encrypted=*/false); }
48+
static void WalletCreateEncrypted(benchmark::Bench& bench) { WalletCreate(bench, /*encrypted=*/true); }
49+
50+
#ifdef USE_SQLITE
51+
BENCHMARK(WalletCreatePlain, benchmark::PriorityLevel::LOW);
52+
BENCHMARK(WalletCreateEncrypted, benchmark::PriorityLevel::LOW);
53+
#endif
54+
55+
} // namespace wallet

src/wallet/external_signer_scriptpubkeyman.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
#include <vector>
1717

1818
namespace wallet {
19-
bool ExternalSignerScriptPubKeyMan::SetupDescriptor(std::unique_ptr<Descriptor> desc)
19+
bool ExternalSignerScriptPubKeyMan::SetupDescriptor(WalletBatch& batch, std::unique_ptr<Descriptor> desc)
2020
{
2121
LOCK(cs_desc_man);
2222
assert(m_storage.IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS));
@@ -29,13 +29,12 @@ bool ExternalSignerScriptPubKeyMan::SetupDescriptor(std::unique_ptr<Descriptor>
2929
m_wallet_descriptor = w_desc;
3030

3131
// Store the descriptor
32-
WalletBatch batch(m_storage.GetDatabase());
3332
if (!batch.WriteDescriptor(GetID(), m_wallet_descriptor)) {
3433
throw std::runtime_error(std::string(__func__) + ": writing descriptor failed");
3534
}
3635

3736
// TopUp
38-
TopUp();
37+
TopUpWithDB(batch);
3938

4039
m_storage.UnsetBlankWalletFlag(batch);
4140
return true;

src/wallet/external_signer_scriptpubkeyman.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class ExternalSignerScriptPubKeyMan : public DescriptorScriptPubKeyMan
2323
/** Provide a descriptor at setup time
2424
* Returns false if already setup or setup fails, true if setup is successful
2525
*/
26-
bool SetupDescriptor(std::unique_ptr<Descriptor>desc);
26+
bool SetupDescriptor(WalletBatch& batch, std::unique_ptr<Descriptor>desc);
2727

2828
static ExternalSigner GetExternalSigner();
2929

src/wallet/scriptpubkeyman.cpp

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,8 @@ bool LegacyScriptPubKeyMan::TopUpInactiveHDChain(const CKeyID seed_id, int64_t i
333333
chain.m_next_external_index = std::max(chain.m_next_external_index, index + 1);
334334
}
335335

336-
TopUpChain(chain, 0);
336+
WalletBatch batch(m_storage.GetDatabase());
337+
TopUpChain(batch, chain, 0);
337338

338339
return true;
339340
}
@@ -1274,19 +1275,22 @@ bool LegacyScriptPubKeyMan::TopUp(unsigned int kpSize)
12741275
return false;
12751276
}
12761277

1277-
if (!TopUpChain(m_hd_chain, kpSize)) {
1278+
WalletBatch batch(m_storage.GetDatabase());
1279+
if (!batch.TxnBegin()) return false;
1280+
if (!TopUpChain(batch, m_hd_chain, kpSize)) {
12781281
return false;
12791282
}
12801283
for (auto& [chain_id, chain] : m_inactive_hd_chains) {
1281-
if (!TopUpChain(chain, kpSize)) {
1284+
if (!TopUpChain(batch, chain, kpSize)) {
12821285
return false;
12831286
}
12841287
}
1288+
if (!batch.TxnCommit()) throw std::runtime_error(strprintf("Error during keypool top up. Cannot commit changes for wallet %s", m_storage.GetDisplayName()));
12851289
NotifyCanGetAddressesChanged();
12861290
return true;
12871291
}
12881292

1289-
bool LegacyScriptPubKeyMan::TopUpChain(CHDChain& chain, unsigned int kpSize)
1293+
bool LegacyScriptPubKeyMan::TopUpChain(WalletBatch& batch, CHDChain& chain, unsigned int kpSize)
12901294
{
12911295
LOCK(cs_KeyStore);
12921296

@@ -1318,7 +1322,6 @@ bool LegacyScriptPubKeyMan::TopUpChain(CHDChain& chain, unsigned int kpSize)
13181322
missingInternal = 0;
13191323
}
13201324
bool internal = false;
1321-
WalletBatch batch(m_storage.GetDatabase());
13221325
for (int64_t i = missingInternal + missingExternal; i--;) {
13231326
if (i < missingInternal) {
13241327
internal = true;
@@ -2135,6 +2138,15 @@ std::map<CKeyID, CKey> DescriptorScriptPubKeyMan::GetKeys() const
21352138
}
21362139

21372140
bool DescriptorScriptPubKeyMan::TopUp(unsigned int size)
2141+
{
2142+
WalletBatch batch(m_storage.GetDatabase());
2143+
if (!batch.TxnBegin()) return false;
2144+
bool res = TopUpWithDB(batch, size);
2145+
if (!batch.TxnCommit()) throw std::runtime_error(strprintf("Error during descriptors keypool top up. Cannot commit changes for wallet %s", m_storage.GetDisplayName()));
2146+
return res;
2147+
}
2148+
2149+
bool DescriptorScriptPubKeyMan::TopUpWithDB(WalletBatch& batch, unsigned int size)
21382150
{
21392151
LOCK(cs_desc_man);
21402152
unsigned int target_size;
@@ -2157,7 +2169,6 @@ bool DescriptorScriptPubKeyMan::TopUp(unsigned int size)
21572169
FlatSigningProvider provider;
21582170
provider.keys = GetKeys();
21592171

2160-
WalletBatch batch(m_storage.GetDatabase());
21612172
uint256 id = GetID();
21622173
for (int32_t i = m_max_cached_index + 1; i < new_range_end; ++i) {
21632174
FlatSigningProvider out_keys;
@@ -2264,7 +2275,7 @@ bool DescriptorScriptPubKeyMan::AddDescriptorKeyWithDB(WalletBatch& batch, const
22642275
}
22652276
}
22662277

2267-
bool DescriptorScriptPubKeyMan::SetupDescriptorGeneration(const CExtKey& master_key, OutputType addr_type, bool internal)
2278+
bool DescriptorScriptPubKeyMan::SetupDescriptorGeneration(WalletBatch& batch, const CExtKey& master_key, OutputType addr_type, bool internal)
22682279
{
22692280
LOCK(cs_desc_man);
22702281
assert(m_storage.IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS));
@@ -2325,7 +2336,6 @@ bool DescriptorScriptPubKeyMan::SetupDescriptorGeneration(const CExtKey& master_
23252336
m_wallet_descriptor = w_desc;
23262337

23272338
// Store the master private key, and descriptor
2328-
WalletBatch batch(m_storage.GetDatabase());
23292339
if (!AddDescriptorKeyWithDB(batch, master_key.key, master_key.key.GetPubKey())) {
23302340
throw std::runtime_error(std::string(__func__) + ": writing descriptor master private key failed");
23312341
}
@@ -2334,7 +2344,7 @@ bool DescriptorScriptPubKeyMan::SetupDescriptorGeneration(const CExtKey& master_
23342344
}
23352345

23362346
// TopUp
2337-
TopUp();
2347+
TopUpWithDB(batch);
23382348

23392349
m_storage.UnsetBlankWalletFlag(batch);
23402350
return true;

src/wallet/scriptpubkeyman.h

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ class LegacyScriptPubKeyMan : public ScriptPubKeyMan, public FillableSigningProv
366366
*/
367367
bool TopUpInactiveHDChain(const CKeyID seed_id, int64_t index, bool internal);
368368

369-
bool TopUpChain(CHDChain& chain, unsigned int size);
369+
bool TopUpChain(WalletBatch& batch, CHDChain& chain, unsigned int size);
370370
public:
371371
LegacyScriptPubKeyMan(WalletStorage& storage, int64_t keypool_size) : ScriptPubKeyMan(storage), m_keypool_size(keypool_size) {}
372372

@@ -583,7 +583,10 @@ class DescriptorScriptPubKeyMan : public ScriptPubKeyMan
583583
std::unique_ptr<FlatSigningProvider> GetSigningProvider(int32_t index, bool include_private = false) const EXCLUSIVE_LOCKS_REQUIRED(cs_desc_man);
584584

585585
protected:
586-
WalletDescriptor m_wallet_descriptor GUARDED_BY(cs_desc_man);
586+
WalletDescriptor m_wallet_descriptor GUARDED_BY(cs_desc_man);
587+
588+
//! Same as 'TopUp' but designed for use within a batch transaction context
589+
bool TopUpWithDB(WalletBatch& batch, unsigned int size = 0);
587590

588591
public:
589592
DescriptorScriptPubKeyMan(WalletStorage& storage, WalletDescriptor& descriptor, int64_t keypool_size)
@@ -618,12 +621,7 @@ class DescriptorScriptPubKeyMan : public ScriptPubKeyMan
618621
bool IsHDEnabled() const override;
619622

620623
//! Setup descriptors based on the given CExtkey
621-
bool SetupDescriptorGeneration(const CExtKey& master_key, OutputType addr_type, bool internal);
622-
623-
/** Provide a descriptor at setup time
624-
* Returns false if already setup or setup fails, true if setup is successful
625-
*/
626-
bool SetupDescriptor(std::unique_ptr<Descriptor>desc);
624+
bool SetupDescriptorGeneration(WalletBatch& batch, const CExtKey& master_key, OutputType addr_type, bool internal);
627625

628626
bool HavePrivateKeys() const override;
629627

src/wallet/wallet.cpp

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3535,23 +3535,30 @@ void CWallet::SetupDescriptorScriptPubKeyMans(const CExtKey& master_key)
35353535
{
35363536
AssertLockHeld(cs_wallet);
35373537

3538+
// Create single batch txn
3539+
WalletBatch batch(GetDatabase());
3540+
if (!batch.TxnBegin()) throw std::runtime_error("Error: cannot create db transaction for descriptors setup");
3541+
35383542
for (bool internal : {false, true}) {
35393543
for (OutputType t : OUTPUT_TYPES) {
35403544
auto spk_manager = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, m_keypool_size));
35413545
if (IsCrypted()) {
35423546
if (IsLocked()) {
35433547
throw std::runtime_error(std::string(__func__) + ": Wallet is locked, cannot setup new descriptors");
35443548
}
3545-
if (!spk_manager->CheckDecryptionKey(vMasterKey) && !spk_manager->Encrypt(vMasterKey, nullptr)) {
3549+
if (!spk_manager->CheckDecryptionKey(vMasterKey) && !spk_manager->Encrypt(vMasterKey, &batch)) {
35463550
throw std::runtime_error(std::string(__func__) + ": Could not encrypt new descriptors");
35473551
}
35483552
}
3549-
spk_manager->SetupDescriptorGeneration(master_key, t, internal);
3553+
spk_manager->SetupDescriptorGeneration(batch, master_key, t, internal);
35503554
uint256 id = spk_manager->GetID();
35513555
AddScriptPubKeyMan(id, std::move(spk_manager));
3552-
AddActiveScriptPubKeyMan(id, t, internal);
3556+
AddActiveScriptPubKeyManWithDb(batch, id, t, internal);
35533557
}
35543558
}
3559+
3560+
// Ensure information is committed to disk
3561+
if (!batch.TxnCommit()) throw std::runtime_error("Error: cannot commit db transaction for descriptors setup");
35553562
}
35563563

35573564
void CWallet::SetupDescriptorScriptPubKeyMans()
@@ -3578,6 +3585,10 @@ void CWallet::SetupDescriptorScriptPubKeyMans()
35783585
UniValue signer_res = signer.GetDescriptors(account);
35793586

35803587
if (!signer_res.isObject()) throw std::runtime_error(std::string(__func__) + ": Unexpected result");
3588+
3589+
WalletBatch batch(GetDatabase());
3590+
if (!batch.TxnBegin()) throw std::runtime_error("Error: cannot create db transaction for descriptors import");
3591+
35813592
for (bool internal : {false, true}) {
35823593
const UniValue& descriptor_vals = signer_res.find_value(internal ? "internal" : "receive");
35833594
if (!descriptor_vals.isArray()) throw std::runtime_error(std::string(__func__) + ": Unexpected result");
@@ -3594,18 +3605,26 @@ void CWallet::SetupDescriptorScriptPubKeyMans()
35943605
}
35953606
OutputType t = *desc->GetOutputType();
35963607
auto spk_manager = std::unique_ptr<ExternalSignerScriptPubKeyMan>(new ExternalSignerScriptPubKeyMan(*this, m_keypool_size));
3597-
spk_manager->SetupDescriptor(std::move(desc));
3608+
spk_manager->SetupDescriptor(batch, std::move(desc));
35983609
uint256 id = spk_manager->GetID();
35993610
AddScriptPubKeyMan(id, std::move(spk_manager));
3600-
AddActiveScriptPubKeyMan(id, t, internal);
3611+
AddActiveScriptPubKeyManWithDb(batch, id, t, internal);
36013612
}
36023613
}
3614+
3615+
// Ensure imported descriptors are committed to disk
3616+
if (!batch.TxnCommit()) throw std::runtime_error("Error: cannot commit db transaction for descriptors import");
36033617
}
36043618
}
36053619

36063620
void CWallet::AddActiveScriptPubKeyMan(uint256 id, OutputType type, bool internal)
36073621
{
36083622
WalletBatch batch(GetDatabase());
3623+
return AddActiveScriptPubKeyManWithDb(batch, id, type, internal);
3624+
}
3625+
3626+
void CWallet::AddActiveScriptPubKeyManWithDb(WalletBatch& batch, uint256 id, OutputType type, bool internal)
3627+
{
36093628
if (!batch.WriteActiveScriptPubKeyMan(static_cast<uint8_t>(type), id, internal)) {
36103629
throw std::runtime_error(std::string(__func__) + ": writing active ScriptPubKeyMan id failed");
36113630
}

src/wallet/wallet.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,9 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati
419419
// Must be the only method adding data to it.
420420
void AddScriptPubKeyMan(const uint256& id, std::unique_ptr<ScriptPubKeyMan> spkm_man);
421421

422+
// Same as 'AddActiveScriptPubKeyMan' but designed for use within a batch transaction context
423+
void AddActiveScriptPubKeyManWithDb(WalletBatch& batch, uint256 id, OutputType type, bool internal);
424+
422425
/**
423426
* Catch wallet up to current chain, scanning new blocks, updating the best
424427
* block locator and m_last_block_processed, and registering for

0 commit comments

Comments
 (0)