Skip to content

Commit 5325a61

Browse files
committed
Merge bitcoin/bitcoin#27224: refactor: Remove CAddressBookData::destdata
a5986e8 refactor: Remove CAddressBookData::destdata (Ryan Ofsky) 5938ad0 wallet: Add DatabaseBatch::ErasePrefix method (Ryan Ofsky) Pull request description: This is cleanup that doesn't change external behavior. Benefits of the cleanup are: - Removes awkward `StringMap` intermediate representation for wallet address metadata. - Simplifies `CWallet`, deals with used address and received request serialization in `walletdb.cpp` instead of higher level wallet code - Adds test coverage and documentation This PR doesn't change externally observable behavior. Internally, the only change in behavior is that `EraseDestData` deletes rows directly from the database because they are no longer stored in memory. This is more direct and efficient because it uses a single lookup and scan instead of multiple lookups. Motivation for this cleanup is making changes like #18550, #18192, #13756 easier to reason about and less likely to result in unintended behavior and bugs --- This PR is a rebased copy of #18608. For some reason that PR is locked and couldn't be reopened or commented on. This PR is an alternative to #27215 with differences described in bitcoin/bitcoin#27215 (review) ACKs for top commit: achow101: ACK a5986e8 furszy: Code ACK a5986e8 Tree-SHA512: 6bd3e402f1f60263fbd433760bdc29d04175ddaf8307207c4a67d59f6cffa732e176ba57886e02926f7a1615dce0ed9cda36c8cbc6430aa8e5b56934c23f3fe7
2 parents d89aca1 + a5986e8 commit 5325a61

File tree

11 files changed

+215
-95
lines changed

11 files changed

+215
-95
lines changed

src/wallet/bdb.cpp

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -668,12 +668,14 @@ void BerkeleyDatabase::ReloadDbEnv()
668668
env->ReloadDbEnv();
669669
}
670670

671-
BerkeleyCursor::BerkeleyCursor(BerkeleyDatabase& database)
671+
BerkeleyCursor::BerkeleyCursor(BerkeleyDatabase& database, BerkeleyBatch* batch)
672672
{
673673
if (!database.m_db.get()) {
674674
throw std::runtime_error(STR_INTERNAL_BUG("BerkeleyDatabase does not exist"));
675675
}
676-
int ret = database.m_db->cursor(nullptr, &m_cursor, 0);
676+
// Transaction argument to cursor is only needed when using the cursor to
677+
// write to the database. Read-only cursors do not need a txn pointer.
678+
int ret = database.m_db->cursor(batch ? batch->txn() : nullptr, &m_cursor, 0);
677679
if (ret != 0) {
678680
throw std::runtime_error(STR_INTERNAL_BUG(strprintf("BDB Cursor could not be created. Returned %d", ret)));
679681
}
@@ -820,6 +822,25 @@ bool BerkeleyBatch::HasKey(DataStream&& key)
820822
return ret == 0;
821823
}
822824

825+
bool BerkeleyBatch::ErasePrefix(Span<const std::byte> prefix)
826+
{
827+
if (!TxnBegin()) return false;
828+
auto cursor{std::make_unique<BerkeleyCursor>(m_database, this)};
829+
// const_cast is safe below even though prefix_key is an in/out parameter,
830+
// because we are not using the DB_DBT_USERMEM flag, so BDB will allocate
831+
// and return a different output data pointer
832+
Dbt prefix_key{const_cast<std::byte*>(prefix.data()), static_cast<uint32_t>(prefix.size())}, prefix_value{};
833+
int ret{cursor->dbc()->get(&prefix_key, &prefix_value, DB_SET_RANGE)};
834+
for (int flag{DB_CURRENT}; ret == 0; flag = DB_NEXT) {
835+
SafeDbt key, value;
836+
ret = cursor->dbc()->get(key, value, flag);
837+
if (ret != 0 || key.get_size() < prefix.size() || memcmp(key.get_data(), prefix.data(), prefix.size()) != 0) break;
838+
ret = cursor->dbc()->del(0);
839+
}
840+
cursor.reset();
841+
return TxnCommit() && (ret == 0 || ret == DB_NOTFOUND);
842+
}
843+
823844
void BerkeleyDatabase::AddRef()
824845
{
825846
LOCK(cs_db);

src/wallet/bdb.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,10 +192,11 @@ class BerkeleyCursor : public DatabaseCursor
192192
Dbc* m_cursor;
193193

194194
public:
195-
explicit BerkeleyCursor(BerkeleyDatabase& database);
195+
explicit BerkeleyCursor(BerkeleyDatabase& database, BerkeleyBatch* batch=nullptr);
196196
~BerkeleyCursor() override;
197197

198198
Status Next(DataStream& key, DataStream& value) override;
199+
Dbc* dbc() const { return m_cursor; }
199200
};
200201

201202
/** RAII class that provides access to a Berkeley database */
@@ -206,6 +207,7 @@ class BerkeleyBatch : public DatabaseBatch
206207
bool WriteKey(DataStream&& key, DataStream&& value, bool overwrite = true) override;
207208
bool EraseKey(DataStream&& key) override;
208209
bool HasKey(DataStream&& key) override;
210+
bool ErasePrefix(Span<const std::byte> prefix) override;
209211

210212
protected:
211213
Db* pdb{nullptr};
@@ -230,6 +232,7 @@ class BerkeleyBatch : public DatabaseBatch
230232
bool TxnBegin() override;
231233
bool TxnCommit() override;
232234
bool TxnAbort() override;
235+
DbTxn* txn() const { return activeTxn; }
233236
};
234237

235238
std::string BerkeleyDatabaseVersion();

src/wallet/db.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ class DatabaseBatch
110110

111111
return HasKey(std::move(ssKey));
112112
}
113+
virtual bool ErasePrefix(Span<const std::byte> prefix) = 0;
113114

114115
virtual std::unique_ptr<DatabaseCursor> GetNewCursor() = 0;
115116
virtual bool TxnBegin() = 0;
@@ -186,6 +187,7 @@ class DummyBatch : public DatabaseBatch
186187
bool WriteKey(DataStream&& key, DataStream&& value, bool overwrite = true) override { return true; }
187188
bool EraseKey(DataStream&& key) override { return true; }
188189
bool HasKey(DataStream&& key) override { return true; }
190+
bool ErasePrefix(Span<const std::byte> prefix) override { return true; }
189191

190192
public:
191193
void Flush() override {}

src/wallet/interfaces.cpp

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,9 +228,22 @@ class WalletImpl : public Wallet
228228
return m_wallet->GetAddressReceiveRequests();
229229
}
230230
bool setAddressReceiveRequest(const CTxDestination& dest, const std::string& id, const std::string& value) override {
231+
// Note: The setAddressReceiveRequest interface used by the GUI to store
232+
// receive requests is a little awkward and could be improved in the
233+
// future:
234+
//
235+
// - The same method is used to save requests and erase them, but
236+
// having separate methods could be clearer and prevent bugs.
237+
//
238+
// - Request ids are passed as strings even though they are generated as
239+
// integers.
240+
//
241+
// - Multiple requests can be stored for the same address, but it might
242+
// be better to only allow one request or only keep the current one.
231243
LOCK(m_wallet->cs_wallet);
232244
WalletBatch batch{m_wallet->GetDatabase()};
233-
return m_wallet->SetAddressReceiveRequest(batch, dest, id, value);
245+
return value.empty() ? m_wallet->EraseAddressReceiveRequest(batch, dest, id)
246+
: m_wallet->SetAddressReceiveRequest(batch, dest, id, value);
234247
}
235248
bool displayAddress(const CTxDestination& dest) override
236249
{

src/wallet/sqlite.cpp

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ void SQLiteBatch::SetupSQLStatements()
125125
{&m_insert_stmt, "INSERT INTO main VALUES(?, ?)"},
126126
{&m_overwrite_stmt, "INSERT or REPLACE into main values(?, ?)"},
127127
{&m_delete_stmt, "DELETE FROM main WHERE key = ?"},
128+
{&m_delete_prefix_stmt, "DELETE FROM main WHERE instr(key, ?) = 1"},
128129
};
129130

130131
for (const auto& [stmt_prepared, stmt_text] : statements) {
@@ -375,6 +376,7 @@ void SQLiteBatch::Close()
375376
{&m_insert_stmt, "insert"},
376377
{&m_overwrite_stmt, "overwrite"},
377378
{&m_delete_stmt, "delete"},
379+
{&m_delete_prefix_stmt, "delete prefix"},
378380
};
379381

380382
for (const auto& [stmt_prepared, stmt_description] : statements) {
@@ -441,24 +443,34 @@ bool SQLiteBatch::WriteKey(DataStream&& key, DataStream&& value, bool overwrite)
441443
return res == SQLITE_DONE;
442444
}
443445

444-
bool SQLiteBatch::EraseKey(DataStream&& key)
446+
bool SQLiteBatch::ExecStatement(sqlite3_stmt* stmt, Span<const std::byte> blob)
445447
{
446448
if (!m_database.m_db) return false;
447-
assert(m_delete_stmt);
449+
assert(stmt);
448450

449451
// Bind: leftmost parameter in statement is index 1
450-
if (!BindBlobToStatement(m_delete_stmt, 1, key, "key")) return false;
452+
if (!BindBlobToStatement(stmt, 1, blob, "key")) return false;
451453

452454
// Execute
453-
int res = sqlite3_step(m_delete_stmt);
454-
sqlite3_clear_bindings(m_delete_stmt);
455-
sqlite3_reset(m_delete_stmt);
455+
int res = sqlite3_step(stmt);
456+
sqlite3_clear_bindings(stmt);
457+
sqlite3_reset(stmt);
456458
if (res != SQLITE_DONE) {
457459
LogPrintf("%s: Unable to execute statement: %s\n", __func__, sqlite3_errstr(res));
458460
}
459461
return res == SQLITE_DONE;
460462
}
461463

464+
bool SQLiteBatch::EraseKey(DataStream&& key)
465+
{
466+
return ExecStatement(m_delete_stmt, key);
467+
}
468+
469+
bool SQLiteBatch::ErasePrefix(Span<const std::byte> prefix)
470+
{
471+
return ExecStatement(m_delete_prefix_stmt, prefix);
472+
}
473+
462474
bool SQLiteBatch::HasKey(DataStream&& key)
463475
{
464476
if (!m_database.m_db) return false;

src/wallet/sqlite.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,16 @@ class SQLiteBatch : public DatabaseBatch
3636
sqlite3_stmt* m_insert_stmt{nullptr};
3737
sqlite3_stmt* m_overwrite_stmt{nullptr};
3838
sqlite3_stmt* m_delete_stmt{nullptr};
39+
sqlite3_stmt* m_delete_prefix_stmt{nullptr};
3940

4041
void SetupSQLStatements();
42+
bool ExecStatement(sqlite3_stmt* stmt, Span<const std::byte> blob);
4143

4244
bool ReadKey(DataStream&& key, DataStream& value) override;
4345
bool WriteKey(DataStream&& key, DataStream&& value, bool overwrite = true) override;
4446
bool EraseKey(DataStream&& key) override;
4547
bool HasKey(DataStream&& key) override;
48+
bool ErasePrefix(Span<const std::byte> prefix) override;
4649

4750
public:
4851
explicit SQLiteBatch(SQLiteDatabase& database);

src/wallet/test/wallet_tests.cpp

Lines changed: 57 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -427,19 +427,63 @@ BOOST_AUTO_TEST_CASE(ComputeTimeSmart)
427427
BOOST_CHECK_EQUAL(AddTx(*m_node.chainman, m_wallet, 5, 50, 600), 300);
428428
}
429429

430-
BOOST_AUTO_TEST_CASE(LoadReceiveRequests)
430+
static const DatabaseFormat DATABASE_FORMATS[] = {
431+
#ifdef USE_SQLITE
432+
DatabaseFormat::SQLITE,
433+
#endif
434+
#ifdef USE_BDB
435+
DatabaseFormat::BERKELEY,
436+
#endif
437+
};
438+
439+
void TestLoadWallet(const std::string& name, DatabaseFormat format, std::function<void(std::shared_ptr<CWallet>)> f)
431440
{
432-
CTxDestination dest = PKHash();
433-
LOCK(m_wallet.cs_wallet);
434-
WalletBatch batch{m_wallet.GetDatabase()};
435-
m_wallet.SetAddressUsed(batch, dest, true);
436-
m_wallet.SetAddressReceiveRequest(batch, dest, "0", "val_rr0");
437-
m_wallet.SetAddressReceiveRequest(batch, dest, "1", "val_rr1");
438-
439-
auto values = m_wallet.GetAddressReceiveRequests();
440-
BOOST_CHECK_EQUAL(values.size(), 2U);
441-
BOOST_CHECK_EQUAL(values[0], "val_rr0");
442-
BOOST_CHECK_EQUAL(values[1], "val_rr1");
441+
node::NodeContext node;
442+
auto chain{interfaces::MakeChain(node)};
443+
DatabaseOptions options;
444+
options.require_format = format;
445+
DatabaseStatus status;
446+
bilingual_str error;
447+
std::vector<bilingual_str> warnings;
448+
auto database{MakeWalletDatabase(name, options, status, error)};
449+
auto wallet{std::make_shared<CWallet>(chain.get(), "", std::move(database))};
450+
BOOST_CHECK_EQUAL(wallet->LoadWallet(), DBErrors::LOAD_OK);
451+
WITH_LOCK(wallet->cs_wallet, f(wallet));
452+
}
453+
454+
BOOST_FIXTURE_TEST_CASE(LoadReceiveRequests, TestingSetup)
455+
{
456+
for (DatabaseFormat format : DATABASE_FORMATS) {
457+
const std::string name{strprintf("receive-requests-%i", format)};
458+
TestLoadWallet(name, format, [](std::shared_ptr<CWallet> wallet) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet) {
459+
BOOST_CHECK(!wallet->IsAddressPreviouslySpent(PKHash()));
460+
WalletBatch batch{wallet->GetDatabase()};
461+
BOOST_CHECK(batch.WriteAddressPreviouslySpent(PKHash(), true));
462+
BOOST_CHECK(batch.WriteAddressPreviouslySpent(ScriptHash(), true));
463+
BOOST_CHECK(wallet->SetAddressReceiveRequest(batch, PKHash(), "0", "val_rr00"));
464+
BOOST_CHECK(wallet->EraseAddressReceiveRequest(batch, PKHash(), "0"));
465+
BOOST_CHECK(wallet->SetAddressReceiveRequest(batch, PKHash(), "1", "val_rr10"));
466+
BOOST_CHECK(wallet->SetAddressReceiveRequest(batch, PKHash(), "1", "val_rr11"));
467+
BOOST_CHECK(wallet->SetAddressReceiveRequest(batch, ScriptHash(), "2", "val_rr20"));
468+
});
469+
TestLoadWallet(name, format, [](std::shared_ptr<CWallet> wallet) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet) {
470+
BOOST_CHECK(wallet->IsAddressPreviouslySpent(PKHash()));
471+
BOOST_CHECK(wallet->IsAddressPreviouslySpent(ScriptHash()));
472+
auto requests = wallet->GetAddressReceiveRequests();
473+
auto erequests = {"val_rr11", "val_rr20"};
474+
BOOST_CHECK_EQUAL_COLLECTIONS(requests.begin(), requests.end(), std::begin(erequests), std::end(erequests));
475+
WalletBatch batch{wallet->GetDatabase()};
476+
BOOST_CHECK(batch.WriteAddressPreviouslySpent(PKHash(), false));
477+
BOOST_CHECK(batch.EraseAddressData(ScriptHash()));
478+
});
479+
TestLoadWallet(name, format, [](std::shared_ptr<CWallet> wallet) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet) {
480+
BOOST_CHECK(!wallet->IsAddressPreviouslySpent(PKHash()));
481+
BOOST_CHECK(!wallet->IsAddressPreviouslySpent(ScriptHash()));
482+
auto requests = wallet->GetAddressReceiveRequests();
483+
auto erequests = {"val_rr11"};
484+
BOOST_CHECK_EQUAL_COLLECTIONS(requests.begin(), requests.end(), std::begin(erequests), std::end(erequests));
485+
});
486+
}
443487
}
444488

445489
// Test some watch-only LegacyScriptPubKeyMan methods by the procedure of loading (LoadWatchOnly),
@@ -922,6 +966,7 @@ class FailBatch : public DatabaseBatch
922966
bool WriteKey(DataStream&& key, DataStream&& value, bool overwrite = true) override { return m_pass; }
923967
bool EraseKey(DataStream&& key) override { return m_pass; }
924968
bool HasKey(DataStream&& key) override { return m_pass; }
969+
bool ErasePrefix(Span<const std::byte> prefix) override { return m_pass; }
925970

926971
public:
927972
explicit FailBatch(bool pass) : m_pass(pass) {}

0 commit comments

Comments
 (0)