Skip to content
Closed
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
23 changes: 23 additions & 0 deletions src/wallet/bdb.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -816,6 +816,29 @@ bool BerkeleyBatch::HasKey(CDataStream&& key)
return ret == 0;
}

bool BerkeleyBatch::ErasePrefix(Span<const std::byte> prefix)
{
if (!TxnBegin()) return false;
if (!StartCursor()) {
TxnAbort();
return false;
}

// 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{m_cursor->get(&prefix_key, &prefix_value, DB_SET_RANGE)};
for (int flag{DB_CURRENT}; ret == 0; flag = DB_NEXT) {
SafeDbt key, value;
ret = m_cursor->get(key, value, flag);
if (ret != 0 || key.get_size() < prefix.size() || memcmp(key.get_data(), prefix.data(), prefix.size()) != 0) break;
ret = m_cursor->del(0);
}
CloseCursor();
return TxnCommit() && (ret == 0 || ret == DB_NOTFOUND);
}

void BerkeleyDatabase::AddRef()
{
LOCK(cs_db);
Expand Down
2 changes: 2 additions & 0 deletions src/wallet/bdb.h
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ class BerkeleyBatch : public DatabaseBatch
bool WriteKey(CDataStream&& key, CDataStream&& value, bool overwrite = true) override;
bool EraseKey(CDataStream&& key) override;
bool HasKey(CDataStream&& key) override;
bool ErasePrefix(Span<const std::byte> prefix) override;

protected:
Db* pdb;
Expand Down Expand Up @@ -221,6 +222,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 @@ -91,6 +91,7 @@ class DatabaseBatch

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

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

public:
void Flush() override {}
Expand Down
4 changes: 3 additions & 1 deletion src/wallet/interfaces.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -257,9 +257,11 @@ class WalletImpl : public Wallet
return m_wallet->GetAddressReceiveRequests();
}
bool setAddressReceiveRequest(const CTxDestination& dest, const std::string& id, const std::string& value) override {
// Note: This method handles both saving and erasing requests based on whether value is empty.
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 lockCoin(const COutPoint& output, const bool write_to_db) 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 @@ -147,6 +147,7 @@ void SQLiteBatch::SetupSQLStatements()
{&m_overwrite_stmt, "INSERT or REPLACE into main values(?, ?)"},
{&m_delete_stmt, "DELETE FROM main WHERE key = ?"},
{&m_cursor_stmt, "SELECT key, value FROM main"},
{&m_delete_prefix_stmt, "DELETE FROM main WHERE instr(key, ?) = 1"},
};

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

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

bool SQLiteBatch::EraseKey(CDataStream&& 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(CDataStream&& 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(CDataStream&& 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 @@ -27,13 +27,16 @@ class SQLiteBatch : public DatabaseBatch
sqlite3_stmt* m_overwrite_stmt{nullptr};
sqlite3_stmt* m_delete_stmt{nullptr};
sqlite3_stmt* m_cursor_stmt{nullptr};
sqlite3_stmt* m_delete_prefix_stmt{nullptr};

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

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

public:
explicit SQLiteBatch(SQLiteDatabase& database);
Expand Down
68 changes: 56 additions & 12 deletions src/wallet/test/wallet_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -441,19 +441,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
83 changes: 35 additions & 48 deletions src/wallet/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -876,11 +876,11 @@ void CWallet::SetSpentKeyState(WalletBatch& batch, const uint256& hash, unsigned
CTxDestination dst;
if (ExtractDestination(srctx->tx->vout[n].scriptPubKey, dst)) {
if (IsMine(dst)) {
if (used != IsAddressUsed(dst)) {
if (used != IsAddressPreviouslySpent(dst)) {
if (used) {
tx_destinations.insert(dst);
}
SetAddressUsed(batch, dst, used);
SetAddressPreviouslySpent(batch, dst, used);
}
}
}
Expand All @@ -893,14 +893,14 @@ bool CWallet::IsSpentKey(const CScript& scriptPubKey) const
if (!ExtractDestination(scriptPubKey, dest)) {
return false;
}
if (IsAddressUsed(dest)) {
if (IsAddressPreviouslySpent(dest)) {
return true;
}
if (IsLegacy()) {
LegacyScriptPubKeyMan* spk_man = GetLegacyScriptPubKeyMan();
assert(spk_man != nullptr);
for (const auto& keyid : GetAffectedKeys(scriptPubKey, *spk_man)) {
if (IsAddressUsed(PKHash(keyid))) {
if (IsAddressPreviouslySpent(PKHash(keyid))) {
return true;
}
}
Expand Down Expand Up @@ -2278,19 +2278,15 @@ bool CWallet::DelAddressBook(const CTxDestination& address)
WalletBatch batch(GetDatabase());
{
LOCK(cs_wallet);
// 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)
// NOTE: This isn't a problem for sending addresses because they never have any DestData yet!
// When adding new DestData, it should be considered here whether to retain or delete it (or move it?).
// 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.
// NOTE: This isn't a problem for sending addresses because they don't have any data that needs to be kept.
// When adding new address data, it should be considered here whether to retain or delete it.
if (IsMine(address)) {
WalletLogPrintf("%s called with IsMine address, NOT SUPPORTED. Please report this bug! %s\n", __func__, PACKAGE_BUGREPORT);
return false;
}
// Delete destdata tuples associated with address
std::string strAddress = EncodeDestination(address);
for (const std::pair<const std::string, std::string> &item : m_address_book[address].destdata)
{
batch.EraseDestData(strAddress, item.first);
}
// Delete data rows associated with this address
batch.EraseAddressData(address);
m_address_book.erase(address);
is_mine = IsMine(address) != ISMINE_NO;
}
Expand Down Expand Up @@ -2689,67 +2685,58 @@ unsigned int CWallet::ComputeTimeSmart(const CWalletTx& wtx, bool rescanning_old
return nTimeSmart;
}

bool CWallet::SetAddressUsed(WalletBatch& batch, const CTxDestination& dest, bool used)
bool CWallet::SetAddressPreviouslySpent(WalletBatch& batch, const CTxDestination& dest, bool used)
{
const std::string key{"used"};
if (std::get_if<CNoDestination>(&dest))
return false;

if (!used) {
if (auto* data = util::FindKey(m_address_book, dest)) data->destdata.erase(key);
return batch.EraseDestData(EncodeDestination(dest), key);
if (auto* data{util::FindKey(m_address_book, dest)}) data->previously_spent = false;
return batch.WriteAddressPreviouslySpent(dest, false);
}

const std::string value{"1"};
m_address_book[dest].destdata.insert(std::make_pair(key, value));
return batch.WriteDestData(EncodeDestination(dest), key, value);
LoadAddressPreviouslySpent(dest);
return batch.WriteAddressPreviouslySpent(dest, true);
}

void CWallet::LoadDestData(const CTxDestination &dest, const std::string &key, const std::string &value)
void CWallet::LoadAddressPreviouslySpent(const CTxDestination& dest)
{
m_address_book[dest].destdata.insert(std::make_pair(key, value));
m_address_book[dest].previously_spent = true;
}

bool CWallet::IsAddressUsed(const CTxDestination& dest) const
void CWallet::LoadAddressReceiveRequest(const CTxDestination& dest, const std::string& id, const std::string& request)
{
const std::string key{"used"};
std::map<CTxDestination, CAddressBookData>::const_iterator i = m_address_book.find(dest);
if(i != m_address_book.end())
{
CAddressBookData::StringMap::const_iterator j = i->second.destdata.find(key);
if(j != i->second.destdata.end())
{
return true;
}
}
m_address_book[dest].receive_requests[id] = request;
}

bool CWallet::IsAddressPreviouslySpent(const CTxDestination& dest) const
{
if (auto* data{util::FindKey(m_address_book, dest)}) return data->previously_spent;
return false;
}

std::vector<std::string> CWallet::GetAddressReceiveRequests() const
{
const std::string prefix{"rr"};
std::vector<std::string> values;
for (const auto& address : m_address_book) {
for (const auto& data : address.second.destdata) {
if (!data.first.compare(0, prefix.size(), prefix)) {
values.emplace_back(data.second);
}
for (const auto& [dest, entry] : m_address_book) {
for (const auto& [id, request] : entry.receive_requests) {
values.emplace_back(request);
}
}
return values;
}

bool CWallet::SetAddressReceiveRequest(WalletBatch& batch, const CTxDestination& dest, const std::string& id, const std::string& value)
{
const std::string key{"rr" + id}; // "rr" prefix = "receive request" in destdata
CAddressBookData& data = m_address_book.at(dest);
if (value.empty()) {
if (!batch.EraseDestData(EncodeDestination(dest), key)) return false;
data.destdata.erase(key);
} else {
if (!batch.WriteDestData(EncodeDestination(dest), key, value)) return false;
data.destdata[key] = value;
}
if (!batch.WriteAddressReceiveRequest(dest, id, value)) return false;
m_address_book[dest].receive_requests[id] = value;
return true;
}

bool CWallet::EraseAddressReceiveRequest(WalletBatch& batch, const CTxDestination& dest, const std::string& id)
{
if (!batch.EraseAddressReceiveRequest(dest, id)) return false;
m_address_book[dest].receive_requests.erase(id);
return true;
}

Expand Down
Loading
Loading