Skip to content

Commit d943c55

Browse files
achow101Claude Code
authored andcommitted
Merge 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 bitcoin#18550, bitcoin#18192, bitcoin#13756 easier to reason about and less likely to result in unintended behavior and bugs --- This PR is a rebased copy of bitcoin#18608. For some reason that PR is locked and couldn't be reopened or commented on. This PR is an alternative to bitcoin#27215 with differences described in bitcoin#27215 (review) ACKs for top commit: achow101: ACK a5986e8 furszy: Code ACK a5986e8 Tree-SHA512: 6bd3e402f1f60263fbd433760bdc29d04175ddaf8307207c4a67d59f6cffa732e176ba57886e02926f7a1615dce0ed9cda36c8cbc6430aa8e5b56934c23f3fe7
1 parent 54e2588 commit d943c55

File tree

11 files changed

+210
-82
lines changed

11 files changed

+210
-82
lines changed

src/wallet/bdb.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -816,6 +816,25 @@ bool BerkeleyBatch::HasKey(CDataStream&& key)
816816
return ret == 0;
817817
}
818818

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

src/wallet/bdb.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ class BerkeleyBatch : public DatabaseBatch
194194
bool WriteKey(CDataStream&& key, CDataStream&& value, bool overwrite = true) override;
195195
bool EraseKey(CDataStream&& key) override;
196196
bool HasKey(CDataStream&& key) override;
197+
bool ErasePrefix(Span<const std::byte> prefix) override;
197198

198199
protected:
199200
Db* pdb;
@@ -221,6 +222,7 @@ class BerkeleyBatch : public DatabaseBatch
221222
bool TxnBegin() override;
222223
bool TxnCommit() override;
223224
bool TxnAbort() override;
225+
DbTxn* txn() const { return activeTxn; }
224226
};
225227

226228
std::string BerkeleyDatabaseVersion();

src/wallet/db.h

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

9292
return HasKey(std::move(ssKey));
9393
}
94+
virtual bool ErasePrefix(Span<const std::byte> prefix) = 0;
9495

9596
virtual bool StartCursor() = 0;
9697
virtual bool ReadAtCursor(CDataStream& ssKey, CDataStream& ssValue, bool& complete) = 0;
@@ -166,6 +167,7 @@ class DummyBatch : public DatabaseBatch
166167
bool WriteKey(CDataStream&& key, CDataStream&& value, bool overwrite=true) override { return true; }
167168
bool EraseKey(CDataStream&& key) override { return true; }
168169
bool HasKey(CDataStream&& key) override { return true; }
170+
bool ErasePrefix(Span<const std::byte> prefix) override { return true; }
169171

170172
public:
171173
void Flush() override {}

src/wallet/interfaces.cpp

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,9 +262,22 @@ class WalletImpl : public Wallet
262262
return m_wallet->GetAddressReceiveRequests();
263263
}
264264
bool setAddressReceiveRequest(const CTxDestination& dest, const std::string& id, const std::string& value) override {
265+
// Note: The setAddressReceiveRequest interface used by the GUI to store
266+
// receive requests is a little awkward and could be improved in the
267+
// future:
268+
//
269+
// - The same method is used to save requests and erase them, but
270+
// having separate methods could be clearer and prevent bugs.
271+
//
272+
// - Request ids are passed as strings even though they are generated as
273+
// integers.
274+
//
275+
// - Multiple requests can be stored for the same address, but it might
276+
// be better to only allow one request or only keep the current one.
265277
LOCK(m_wallet->cs_wallet);
266278
WalletBatch batch{m_wallet->GetDatabase()};
267-
return m_wallet->SetAddressReceiveRequest(batch, dest, id, value);
279+
return value.empty() ? m_wallet->EraseAddressReceiveRequest(batch, dest, id)
280+
: m_wallet->SetAddressReceiveRequest(batch, dest, id, value);
268281
}
269282
bool lockCoin(const COutPoint& output, const bool write_to_db) override
270283
{

src/wallet/sqlite.cpp

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ void SQLiteBatch::SetupSQLStatements()
146146
{&m_insert_stmt, "INSERT INTO main VALUES(?, ?)"},
147147
{&m_overwrite_stmt, "INSERT or REPLACE into main values(?, ?)"},
148148
{&m_delete_stmt, "DELETE FROM main WHERE key = ?"},
149+
{&m_delete_prefix_stmt, "DELETE FROM main WHERE instr(key, ?) = 1"},
149150
{&m_cursor_stmt, "SELECT key, value FROM main"},
150151
};
151152

@@ -404,6 +405,7 @@ void SQLiteBatch::Close()
404405
{&m_insert_stmt, "insert"},
405406
{&m_overwrite_stmt, "overwrite"},
406407
{&m_delete_stmt, "delete"},
408+
{&m_delete_prefix_stmt, "delete prefix"},
407409
{&m_cursor_stmt, "cursor"},
408410
};
409411

@@ -470,24 +472,34 @@ bool SQLiteBatch::WriteKey(CDataStream&& key, CDataStream&& value, bool overwrit
470472
return res == SQLITE_DONE;
471473
}
472474

473-
bool SQLiteBatch::EraseKey(CDataStream&& key)
475+
bool SQLiteBatch::ExecStatement(sqlite3_stmt* stmt, Span<const std::byte> blob)
474476
{
475477
if (!m_database.m_db) return false;
476-
assert(m_delete_stmt);
478+
assert(stmt);
477479

478480
// Bind: leftmost parameter in statement is index 1
479-
if (!BindBlobToStatement(m_delete_stmt, 1, key, "key")) return false;
481+
if (!BindBlobToStatement(stmt, 1, blob, "key")) return false;
480482

481483
// Execute
482-
int res = sqlite3_step(m_delete_stmt);
483-
sqlite3_clear_bindings(m_delete_stmt);
484-
sqlite3_reset(m_delete_stmt);
484+
int res = sqlite3_step(stmt);
485+
sqlite3_clear_bindings(stmt);
486+
sqlite3_reset(stmt);
485487
if (res != SQLITE_DONE) {
486488
LogPrintf("%s: Unable to execute statement: %s\n", __func__, sqlite3_errstr(res));
487489
}
488490
return res == SQLITE_DONE;
489491
}
490492

493+
bool SQLiteBatch::EraseKey(CDataStream&& key)
494+
{
495+
return ExecStatement(m_delete_stmt, key);
496+
}
497+
498+
bool SQLiteBatch::ErasePrefix(Span<const std::byte> prefix)
499+
{
500+
return ExecStatement(m_delete_prefix_stmt, prefix);
501+
}
502+
491503
bool SQLiteBatch::HasKey(CDataStream&& key)
492504
{
493505
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
@@ -28,14 +28,17 @@ class SQLiteBatch : public DatabaseBatch
2828
sqlite3_stmt* m_insert_stmt{nullptr};
2929
sqlite3_stmt* m_overwrite_stmt{nullptr};
3030
sqlite3_stmt* m_delete_stmt{nullptr};
31+
sqlite3_stmt* m_delete_prefix_stmt{nullptr};
3132
sqlite3_stmt* m_cursor_stmt{nullptr};
3233

3334
void SetupSQLStatements();
35+
bool ExecStatement(sqlite3_stmt* stmt, Span<const std::byte> blob);
3436

3537
bool ReadKey(CDataStream&& key, CDataStream& value) override;
3638
bool WriteKey(CDataStream&& key, CDataStream&& value, bool overwrite = true) override;
3739
bool EraseKey(CDataStream&& key) override;
3840
bool HasKey(CDataStream&& key) override;
41+
bool ErasePrefix(Span<const std::byte> prefix) override;
3942

4043
public:
4144
explicit SQLiteBatch(SQLiteDatabase& database);

src/wallet/test/wallet_tests.cpp

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

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

458502
// Test some watch-only LegacyScriptPubKeyMan methods by the procedure of loading (LoadWatchOnly),
@@ -1488,6 +1532,7 @@ class FailBatch : public DatabaseBatch
14881532
bool WriteKey(CDataStream&& key, CDataStream&& value, bool overwrite=true) override { return m_pass; }
14891533
bool EraseKey(CDataStream&& key) override { return m_pass; }
14901534
bool HasKey(CDataStream&& key) override { return m_pass; }
1535+
bool ErasePrefix(Span<const std::byte> prefix) override { return m_pass; }
14911536

14921537
public:
14931538
explicit FailBatch(bool pass) : m_pass(pass) {}

src/wallet/wallet.cpp

Lines changed: 36 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -882,11 +882,11 @@ void CWallet::SetSpentKeyState(WalletBatch& batch, const uint256& hash, unsigned
882882
CTxDestination dst;
883883
if (ExtractDestination(srctx->tx->vout[n].scriptPubKey, dst)) {
884884
if (IsMine(dst)) {
885-
if (used != IsAddressUsed(dst)) {
885+
if (used != IsAddressPreviouslySpent(dst)) {
886886
if (used) {
887887
tx_destinations.insert(dst);
888888
}
889-
SetAddressUsed(batch, dst, used);
889+
SetAddressPreviouslySpent(batch, dst, used);
890890
}
891891
}
892892
}
@@ -899,14 +899,15 @@ bool CWallet::IsSpentKey(const CScript& scriptPubKey) const
899899
if (!ExtractDestination(scriptPubKey, dest)) {
900900
return false;
901901
}
902-
if (IsAddressUsed(dest)) {
902+
if (IsAddressPreviouslySpent(dest)) {
903903
return true;
904904
}
905905
if (IsLegacy()) {
906906
LegacyScriptPubKeyMan* spk_man = GetLegacyScriptPubKeyMan();
907907
assert(spk_man != nullptr);
908908
for (const auto& keyid : GetAffectedKeys(scriptPubKey, *spk_man)) {
909-
if (IsAddressUsed(PKHash(keyid))) {
909+
PKHash pkh_dest(keyid);
910+
if (IsAddressPreviouslySpent(pkh_dest)) {
910911
return true;
911912
}
912913
}
@@ -2290,19 +2291,15 @@ bool CWallet::DelAddressBook(const CTxDestination& address)
22902291
WalletBatch batch(GetDatabase());
22912292
{
22922293
LOCK(cs_wallet);
2293-
// If we want to delete receiving addresses, we need to take care that DestData "used" (and possibly newer DestData) gets preserved (and the "deleted" address transformed into a change entry instead of actually being deleted)
2294-
// NOTE: This isn't a problem for sending addresses because they never have any DestData yet!
2295-
// When adding new DestData, it should be considered here whether to retain or delete it (or move it?).
2294+
// If we want to delete receiving addresses, we should avoid calling EraseAddressData because it will delete the previously_spent value. Could instead just erase the label so it becomes a change address, and keep the data.
2295+
// NOTE: This isn't a problem for sending addresses because they don't have any data that needs to be kept.
2296+
// When adding new address data, it should be considered here whether to retain or delete it.
22962297
if (IsMine(address)) {
22972298
WalletLogPrintf("%s called with IsMine address, NOT SUPPORTED. Please report this bug! %s\n", __func__, PACKAGE_BUGREPORT);
22982299
return false;
22992300
}
2300-
// Delete destdata tuples associated with address
2301-
std::string strAddress = EncodeDestination(address);
2302-
for (const std::pair<const std::string, std::string> &item : m_address_book[address].destdata)
2303-
{
2304-
batch.EraseDestData(strAddress, item.first);
2305-
}
2301+
// Delete data rows associated with this address
2302+
batch.EraseAddressData(address);
23062303
m_address_book.erase(address);
23072304
is_mine = IsMine(address) != ISMINE_NO;
23082305
}
@@ -2693,67 +2690,58 @@ unsigned int CWallet::ComputeTimeSmart(const CWalletTx& wtx, bool rescanning_old
26932690
return nTimeSmart;
26942691
}
26952692

2696-
bool CWallet::SetAddressUsed(WalletBatch& batch, const CTxDestination& dest, bool used)
2693+
bool CWallet::SetAddressPreviouslySpent(WalletBatch& batch, const CTxDestination& dest, bool used)
26972694
{
2698-
const std::string key{"used"};
26992695
if (std::get_if<CNoDestination>(&dest))
27002696
return false;
27012697

27022698
if (!used) {
2703-
if (auto* data = util::FindKey(m_address_book, dest)) data->destdata.erase(key);
2704-
return batch.EraseDestData(EncodeDestination(dest), key);
2699+
if (auto* data{util::FindKey(m_address_book, dest)}) data->previously_spent = false;
2700+
return batch.WriteAddressPreviouslySpent(dest, false);
27052701
}
27062702

2707-
const std::string value{"1"};
2708-
m_address_book[dest].destdata.insert(std::make_pair(key, value));
2709-
return batch.WriteDestData(EncodeDestination(dest), key, value);
2703+
LoadAddressPreviouslySpent(dest);
2704+
return batch.WriteAddressPreviouslySpent(dest, true);
27102705
}
27112706

2712-
void CWallet::LoadDestData(const CTxDestination &dest, const std::string &key, const std::string &value)
2707+
void CWallet::LoadAddressPreviouslySpent(const CTxDestination& dest)
27132708
{
2714-
m_address_book[dest].destdata.insert(std::make_pair(key, value));
2709+
m_address_book[dest].previously_spent = true;
27152710
}
27162711

2717-
bool CWallet::IsAddressUsed(const CTxDestination& dest) const
2712+
void CWallet::LoadAddressReceiveRequest(const CTxDestination& dest, const std::string& id, const std::string& request)
27182713
{
2719-
const std::string key{"used"};
2720-
std::map<CTxDestination, CAddressBookData>::const_iterator i = m_address_book.find(dest);
2721-
if(i != m_address_book.end())
2722-
{
2723-
CAddressBookData::StringMap::const_iterator j = i->second.destdata.find(key);
2724-
if(j != i->second.destdata.end())
2725-
{
2726-
return true;
2727-
}
2728-
}
2714+
m_address_book[dest].receive_requests[id] = request;
2715+
}
2716+
2717+
bool CWallet::IsAddressPreviouslySpent(const CTxDestination& dest) const
2718+
{
2719+
if (auto* data{util::FindKey(m_address_book, dest)}) return data->previously_spent;
27292720
return false;
27302721
}
27312722

27322723
std::vector<std::string> CWallet::GetAddressReceiveRequests() const
27332724
{
2734-
const std::string prefix{"rr"};
27352725
std::vector<std::string> values;
2736-
for (const auto& address : m_address_book) {
2737-
for (const auto& data : address.second.destdata) {
2738-
if (!data.first.compare(0, prefix.size(), prefix)) {
2739-
values.emplace_back(data.second);
2740-
}
2726+
for (const auto& [dest, entry] : m_address_book) {
2727+
for (const auto& [id, request] : entry.receive_requests) {
2728+
values.emplace_back(request);
27412729
}
27422730
}
27432731
return values;
27442732
}
27452733

27462734
bool CWallet::SetAddressReceiveRequest(WalletBatch& batch, const CTxDestination& dest, const std::string& id, const std::string& value)
27472735
{
2748-
const std::string key{"rr" + id}; // "rr" prefix = "receive request" in destdata
2749-
CAddressBookData& data = m_address_book.at(dest);
2750-
if (value.empty()) {
2751-
if (!batch.EraseDestData(EncodeDestination(dest), key)) return false;
2752-
data.destdata.erase(key);
2753-
} else {
2754-
if (!batch.WriteDestData(EncodeDestination(dest), key, value)) return false;
2755-
data.destdata[key] = value;
2756-
}
2736+
if (!batch.WriteAddressReceiveRequest(dest, id, value)) return false;
2737+
m_address_book[dest].receive_requests[id] = value;
2738+
return true;
2739+
}
2740+
2741+
bool CWallet::EraseAddressReceiveRequest(WalletBatch& batch, const CTxDestination& dest, const std::string& id)
2742+
{
2743+
if (!batch.EraseAddressReceiveRequest(dest, id)) return false;
2744+
m_address_book[dest].receive_requests.erase(id);
27572745
return true;
27582746
}
27592747

0 commit comments

Comments
 (0)