Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions src/wallet/bdb.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -665,12 +665,14 @@ void BerkeleyDatabase::ReloadDbEnv()
env->ReloadDbEnv();
}

BerkeleyCursor::BerkeleyCursor(BerkeleyDatabase& database)
BerkeleyCursor::BerkeleyCursor(BerkeleyDatabase& database, BerkeleyBatch* batch)
{
if (!database.m_db.get()) {
throw std::runtime_error(STR_INTERNAL_BUG("BerkeleyDatabase does not exist"));
}
int ret = database.m_db->cursor(nullptr, &m_cursor, 0);
// Transaction argument to cursor is only needed when using the cursor to
// write to the database. Read-only cursors do not need a txn pointer.
int ret = database.m_db->cursor(batch ? batch->txn() : nullptr, &m_cursor, 0);
Comment on lines +673 to +675
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this also fixes abdef47f206dec114300b2d852f5c297b03179e3 (from #26644)

if (ret != 0) {
throw std::runtime_error(STR_INTERNAL_BUG(strprintf("BDB Cursor could not be created. Returned %d", ret)));
}
Expand Down Expand Up @@ -817,6 +819,25 @@ bool BerkeleyBatch::HasKey(DataStream&& key)
return ret == 0;
}

bool BerkeleyBatch::ErasePrefix(Span<const std::byte> prefix)
{
if (!TxnBegin()) return false;
auto cursor{std::make_unique<BerkeleyCursor>(m_database, this)};
// const_cast is safe below even though prefix_key is an in/out parameter,
// because we are not using the DB_DBT_USERMEM flag, so BDB will allocate
// and return a different output data pointer
Dbt prefix_key{const_cast<std::byte*>(prefix.data()), static_cast<uint32_t>(prefix.size())}, prefix_value{};
int ret{cursor->dbc()->get(&prefix_key, &prefix_value, DB_SET_RANGE)};
for (int flag{DB_CURRENT}; ret == 0; flag = DB_NEXT) {
SafeDbt key, value;
ret = cursor->dbc()->get(key, value, flag);
if (ret != 0 || key.get_size() < prefix.size() || memcmp(key.get_data(), prefix.data(), prefix.size()) != 0) break;
ret = cursor->dbc()->del(0);
}
cursor.reset();
return TxnCommit() && (ret == 0 || ret == DB_NOTFOUND);
}

void BerkeleyDatabase::AddRef()
{
LOCK(cs_db);
Expand Down
5 changes: 4 additions & 1 deletion src/wallet/bdb.h
Original file line number Diff line number Diff line change
Expand Up @@ -191,10 +191,11 @@ class BerkeleyCursor : public DatabaseCursor
Dbc* m_cursor;

public:
explicit BerkeleyCursor(BerkeleyDatabase& database);
explicit BerkeleyCursor(BerkeleyDatabase& database, BerkeleyBatch* batch=nullptr);
~BerkeleyCursor() override;

Status Next(DataStream& key, DataStream& value) override;
Dbc* dbc() const { return m_cursor; }
};

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

protected:
Db* pdb{nullptr};
Expand All @@ -229,6 +231,7 @@ class BerkeleyBatch : public DatabaseBatch
bool TxnBegin() override;
bool TxnCommit() override;
bool TxnAbort() override;
DbTxn* txn() const { return activeTxn; }
};

std::string BerkeleyDatabaseVersion();
Expand Down
2 changes: 2 additions & 0 deletions src/wallet/db.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ class DatabaseBatch

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

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

public:
void Flush() override {}
Expand Down
15 changes: 14 additions & 1 deletion src/wallet/interfaces.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -228,9 +228,22 @@ class WalletImpl : public Wallet
return m_wallet->GetAddressReceiveRequests();
}
bool setAddressReceiveRequest(const CTxDestination& dest, const std::string& id, const std::string& value) override {
// Note: The setAddressReceiveRequest interface used by the GUI to store
// receive requests is a little awkward and could be improved in the
// future:
//
// - The same method is used to save requests and erase them, but
// having separate methods could be clearer and prevent bugs.
//
// - Request ids are passed as strings even though they are generated as
// integers.
//
// - Multiple requests can be stored for the same address, but it might
// be better to only allow one request or only keep the current one.
LOCK(m_wallet->cs_wallet);
WalletBatch batch{m_wallet->GetDatabase()};
return m_wallet->SetAddressReceiveRequest(batch, dest, id, value);
return value.empty() ? m_wallet->EraseAddressReceiveRequest(batch, dest, id)
: m_wallet->SetAddressReceiveRequest(batch, dest, id, value);
}
bool displayAddress(const CTxDestination& dest) override
{
Expand Down
24 changes: 18 additions & 6 deletions src/wallet/sqlite.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ void SQLiteBatch::SetupSQLStatements()
{&m_insert_stmt, "INSERT INTO main VALUES(?, ?)"},
{&m_overwrite_stmt, "INSERT or REPLACE into main values(?, ?)"},
{&m_delete_stmt, "DELETE FROM main WHERE key = ?"},
{&m_delete_prefix_stmt, "DELETE FROM main WHERE instr(key, ?) = 1"},
};

for (const auto& [stmt_prepared, stmt_text] : statements) {
Expand Down Expand Up @@ -375,6 +376,7 @@ void SQLiteBatch::Close()
{&m_insert_stmt, "insert"},
{&m_overwrite_stmt, "overwrite"},
{&m_delete_stmt, "delete"},
{&m_delete_prefix_stmt, "delete prefix"},
};

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

bool SQLiteBatch::EraseKey(DataStream&& key)
bool SQLiteBatch::ExecStatement(sqlite3_stmt* stmt, Span<const std::byte> blob)
{
if (!m_database.m_db) return false;
assert(m_delete_stmt);
assert(stmt);

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

// Execute
int res = sqlite3_step(m_delete_stmt);
sqlite3_clear_bindings(m_delete_stmt);
sqlite3_reset(m_delete_stmt);
int res = sqlite3_step(stmt);
sqlite3_clear_bindings(stmt);
sqlite3_reset(stmt);
if (res != SQLITE_DONE) {
LogPrintf("%s: Unable to execute statement: %s\n", __func__, sqlite3_errstr(res));
}
return res == SQLITE_DONE;
}

bool SQLiteBatch::EraseKey(DataStream&& key)
{
return ExecStatement(m_delete_stmt, key);
}

bool SQLiteBatch::ErasePrefix(Span<const std::byte> prefix)
{
return ExecStatement(m_delete_prefix_stmt, prefix);
}

bool SQLiteBatch::HasKey(DataStream&& key)
{
if (!m_database.m_db) return false;
Expand Down
3 changes: 3 additions & 0 deletions src/wallet/sqlite.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,16 @@ class SQLiteBatch : public DatabaseBatch
sqlite3_stmt* m_insert_stmt{nullptr};
sqlite3_stmt* m_overwrite_stmt{nullptr};
sqlite3_stmt* m_delete_stmt{nullptr};
sqlite3_stmt* m_delete_prefix_stmt{nullptr};

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

bool ReadKey(DataStream&& key, DataStream& value) override;
bool WriteKey(DataStream&& key, DataStream&& value, bool overwrite = true) override;
bool EraseKey(DataStream&& key) override;
bool HasKey(DataStream&& key) override;
bool ErasePrefix(Span<const std::byte> prefix) override;

public:
explicit SQLiteBatch(SQLiteDatabase& database);
Expand Down
69 changes: 57 additions & 12 deletions src/wallet/test/wallet_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -427,19 +427,63 @@ BOOST_AUTO_TEST_CASE(ComputeTimeSmart)
BOOST_CHECK_EQUAL(AddTx(*m_node.chainman, m_wallet, 5, 50, 600), 300);
}

BOOST_AUTO_TEST_CASE(LoadReceiveRequests)
static const DatabaseFormat DATABASE_FORMATS[] = {
#ifdef USE_SQLITE
DatabaseFormat::SQLITE,
#endif
#ifdef USE_BDB
DatabaseFormat::BERKELEY,
#endif
};

void TestLoadWallet(const std::string& name, DatabaseFormat format, std::function<void(std::shared_ptr<CWallet>)> f)
{
CTxDestination dest = PKHash();
LOCK(m_wallet.cs_wallet);
WalletBatch batch{m_wallet.GetDatabase()};
m_wallet.SetAddressUsed(batch, dest, true);
m_wallet.SetAddressReceiveRequest(batch, dest, "0", "val_rr0");
m_wallet.SetAddressReceiveRequest(batch, dest, "1", "val_rr1");

auto values = m_wallet.GetAddressReceiveRequests();
BOOST_CHECK_EQUAL(values.size(), 2U);
BOOST_CHECK_EQUAL(values[0], "val_rr0");
BOOST_CHECK_EQUAL(values[1], "val_rr1");
node::NodeContext node;
auto chain{interfaces::MakeChain(node)};
DatabaseOptions options;
options.require_format = format;
DatabaseStatus status;
bilingual_str error;
std::vector<bilingual_str> warnings;
auto database{MakeWalletDatabase(name, options, status, error)};
auto wallet{std::make_shared<CWallet>(chain.get(), "", std::move(database))};
BOOST_CHECK_EQUAL(wallet->LoadWallet(), DBErrors::LOAD_OK);
WITH_LOCK(wallet->cs_wallet, f(wallet));
}

BOOST_FIXTURE_TEST_CASE(LoadReceiveRequests, TestingSetup)
{
for (DatabaseFormat format : DATABASE_FORMATS) {
const std::string name{strprintf("receive-requests-%i", format)};
TestLoadWallet(name, format, [](std::shared_ptr<CWallet> wallet) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet) {
BOOST_CHECK(!wallet->IsAddressPreviouslySpent(PKHash()));
WalletBatch batch{wallet->GetDatabase()};
BOOST_CHECK(batch.WriteAddressPreviouslySpent(PKHash(), true));
BOOST_CHECK(batch.WriteAddressPreviouslySpent(ScriptHash(), true));
BOOST_CHECK(wallet->SetAddressReceiveRequest(batch, PKHash(), "0", "val_rr00"));
BOOST_CHECK(wallet->EraseAddressReceiveRequest(batch, PKHash(), "0"));
BOOST_CHECK(wallet->SetAddressReceiveRequest(batch, PKHash(), "1", "val_rr10"));
BOOST_CHECK(wallet->SetAddressReceiveRequest(batch, PKHash(), "1", "val_rr11"));
BOOST_CHECK(wallet->SetAddressReceiveRequest(batch, ScriptHash(), "2", "val_rr20"));
});
TestLoadWallet(name, format, [](std::shared_ptr<CWallet> wallet) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet) {
BOOST_CHECK(wallet->IsAddressPreviouslySpent(PKHash()));
BOOST_CHECK(wallet->IsAddressPreviouslySpent(ScriptHash()));
auto requests = wallet->GetAddressReceiveRequests();
auto erequests = {"val_rr11", "val_rr20"};
BOOST_CHECK_EQUAL_COLLECTIONS(requests.begin(), requests.end(), std::begin(erequests), std::end(erequests));
WalletBatch batch{wallet->GetDatabase()};
BOOST_CHECK(batch.WriteAddressPreviouslySpent(PKHash(), false));
BOOST_CHECK(batch.EraseAddressData(ScriptHash()));
});
TestLoadWallet(name, format, [](std::shared_ptr<CWallet> wallet) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet) {
BOOST_CHECK(!wallet->IsAddressPreviouslySpent(PKHash()));
BOOST_CHECK(!wallet->IsAddressPreviouslySpent(ScriptHash()));
auto requests = wallet->GetAddressReceiveRequests();
auto erequests = {"val_rr11"};
BOOST_CHECK_EQUAL_COLLECTIONS(requests.begin(), requests.end(), std::begin(erequests), std::end(erequests));
});
}
}

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

public:
explicit FailBatch(bool pass) : m_pass(pass) {}
Expand Down
Loading