mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-04-23 23:12:20 +02:00
Merge bitcoin/bitcoin#27224: refactor: Remove CAddressBookData::destdata
a5986e82dd2b8f923d60f9e31760ef62b9010105 refactor: Remove CAddressBookData::destdata (Ryan Ofsky) 5938ad0bdb013953861c7cd15a95f00998a06f44 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 https://github.com/bitcoin/bitcoin/pull/27215#pullrequestreview-1329028143 ACKs for top commit: achow101: ACK a5986e82dd2b8f923d60f9e31760ef62b9010105 furszy: Code ACK a5986e82 Tree-SHA512: 6bd3e402f1f60263fbd433760bdc29d04175ddaf8307207c4a67d59f6cffa732e176ba57886e02926f7a1615dce0ed9cda36c8cbc6430aa8e5b56934c23f3fe7
This commit is contained in:
commit
5325a61167
@ -668,12 +668,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);
|
||||
if (ret != 0) {
|
||||
throw std::runtime_error(STR_INTERNAL_BUG(strprintf("BDB Cursor could not be created. Returned %d", ret)));
|
||||
}
|
||||
@ -820,6 +822,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);
|
||||
|
@ -192,10 +192,11 @@ private:
|
||||
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 */
|
||||
@ -206,6 +207,7 @@ private:
|
||||
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};
|
||||
@ -230,6 +232,7 @@ public:
|
||||
bool TxnBegin() override;
|
||||
bool TxnCommit() override;
|
||||
bool TxnAbort() override;
|
||||
DbTxn* txn() const { return activeTxn; }
|
||||
};
|
||||
|
||||
std::string BerkeleyDatabaseVersion();
|
||||
|
@ -110,6 +110,7 @@ public:
|
||||
|
||||
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;
|
||||
@ -186,6 +187,7 @@ private:
|
||||
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 {}
|
||||
|
@ -228,9 +228,22 @@ public:
|
||||
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
|
||||
{
|
||||
|
@ -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) {
|
||||
@ -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) {
|
||||
@ -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;
|
||||
|
@ -36,13 +36,16 @@ private:
|
||||
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);
|
||||
|
@ -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)
|
||||
{
|
||||
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");
|
||||
static const DatabaseFormat DATABASE_FORMATS[] = {
|
||||
#ifdef USE_SQLITE
|
||||
DatabaseFormat::SQLITE,
|
||||
#endif
|
||||
#ifdef USE_BDB
|
||||
DatabaseFormat::BERKELEY,
|
||||
#endif
|
||||
};
|
||||
|
||||
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");
|
||||
void TestLoadWallet(const std::string& name, DatabaseFormat format, std::function<void(std::shared_ptr<CWallet>)> f)
|
||||
{
|
||||
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),
|
||||
@ -922,6 +966,7 @@ private:
|
||||
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) {}
|
||||
|
@ -967,11 +967,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -984,7 +984,7 @@ bool CWallet::IsSpentKey(const CScript& scriptPubKey) const
|
||||
if (!ExtractDestination(scriptPubKey, dest)) {
|
||||
return false;
|
||||
}
|
||||
if (IsAddressUsed(dest)) {
|
||||
if (IsAddressPreviouslySpent(dest)) {
|
||||
return true;
|
||||
}
|
||||
if (IsLegacy()) {
|
||||
@ -992,15 +992,15 @@ bool CWallet::IsSpentKey(const CScript& scriptPubKey) const
|
||||
assert(spk_man != nullptr);
|
||||
for (const auto& keyid : GetAffectedKeys(scriptPubKey, *spk_man)) {
|
||||
WitnessV0KeyHash wpkh_dest(keyid);
|
||||
if (IsAddressUsed(wpkh_dest)) {
|
||||
if (IsAddressPreviouslySpent(wpkh_dest)) {
|
||||
return true;
|
||||
}
|
||||
ScriptHash sh_wpkh_dest(GetScriptForDestination(wpkh_dest));
|
||||
if (IsAddressUsed(sh_wpkh_dest)) {
|
||||
if (IsAddressPreviouslySpent(sh_wpkh_dest)) {
|
||||
return true;
|
||||
}
|
||||
PKHash pkh_dest(keyid);
|
||||
if (IsAddressUsed(pkh_dest)) {
|
||||
if (IsAddressPreviouslySpent(pkh_dest)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -2403,19 +2403,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);
|
||||
}
|
||||
|
||||
@ -2790,51 +2786,42 @@ 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;
|
||||
@ -2842,15 +2829,15 @@ std::vector<std::string> CWallet::GetAddressReceiveRequests() const
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -221,17 +221,22 @@ struct CAddressBookData
|
||||
std::optional<AddressPurpose> purpose;
|
||||
|
||||
/**
|
||||
* Additional address metadata map that can currently hold two types of keys:
|
||||
*
|
||||
* "used" keys with values always set to "1" or "p" if present. This is set on
|
||||
* IsMine addresses that have already been spent from if the
|
||||
* avoid_reuse option is enabled
|
||||
*
|
||||
* "rr##" keys where ## is a decimal number, with serialized
|
||||
* RecentRequestEntry objects as values
|
||||
* Whether coins with this address have previously been spent. Set when the
|
||||
* the wallet avoid_reuse option is enabled and this is an IsMine address
|
||||
* that has already received funds and spent them. This is used during coin
|
||||
* selection to increase privacy by not creating different transactions
|
||||
* that spend from the same addresses.
|
||||
*/
|
||||
typedef std::map<std::string, std::string> StringMap;
|
||||
StringMap destdata;
|
||||
bool previously_spent{false};
|
||||
|
||||
/**
|
||||
* Map containing data about previously generated receive requests
|
||||
* requesting funds to be sent to this address. Only present for IsMine
|
||||
* addresses. Map keys are decimal numbers uniquely identifying each
|
||||
* request, and map values are serialized RecentRequestEntry objects
|
||||
* containing BIP21 URI information including message and amount.
|
||||
*/
|
||||
std::map<std::string, std::string> receive_requests{};
|
||||
|
||||
/** Accessor methods. */
|
||||
bool IsChange() const { return !label.has_value(); }
|
||||
@ -511,8 +516,10 @@ public:
|
||||
|
||||
bool LoadMinVersion(int nVersion) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); nWalletVersion = nVersion; return true; }
|
||||
|
||||
//! Adds a destination data tuple to the store, without saving it to disk
|
||||
void LoadDestData(const CTxDestination& dest, const std::string& key, const std::string& value) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
//! Marks destination as previously spent.
|
||||
void LoadAddressPreviouslySpent(const CTxDestination& dest) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
//! Appends payment request to destination.
|
||||
void LoadAddressReceiveRequest(const CTxDestination& dest, const std::string& id, const std::string& request) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
|
||||
//! Holds a timestamp at which point the wallet is scheduled (externally) to be relocked. Caller must arrange for actual relocking to occur via Lock().
|
||||
int64_t nRelockTime GUARDED_BY(cs_wallet){0};
|
||||
@ -739,11 +746,12 @@ public:
|
||||
|
||||
bool DelAddressBook(const CTxDestination& address);
|
||||
|
||||
bool IsAddressUsed(const CTxDestination& dest) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
bool SetAddressUsed(WalletBatch& batch, const CTxDestination& dest, bool used) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
bool IsAddressPreviouslySpent(const CTxDestination& dest) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
bool SetAddressPreviouslySpent(WalletBatch& batch, const CTxDestination& dest, bool used) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
|
||||
std::vector<std::string> GetAddressReceiveRequests() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
bool SetAddressReceiveRequest(WalletBatch& batch, const CTxDestination& dest, const std::string& id, const std::string& value) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
bool EraseAddressReceiveRequest(WalletBatch& batch, const CTxDestination& dest, const std::string& id) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
|
||||
unsigned int GetKeyPoolSize() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
|
||||
|
@ -615,7 +615,20 @@ ReadKeyValue(CWallet* pwallet, DataStream& ssKey, CDataStream& ssValue,
|
||||
ssKey >> strAddress;
|
||||
ssKey >> strKey;
|
||||
ssValue >> strValue;
|
||||
pwallet->LoadDestData(DecodeDestination(strAddress), strKey, strValue);
|
||||
const CTxDestination& dest{DecodeDestination(strAddress)};
|
||||
if (strKey.compare("used") == 0) {
|
||||
// Load "used" key indicating if an IsMine address has
|
||||
// previously been spent from with avoid_reuse option enabled.
|
||||
// The strValue is not used for anything currently, but could
|
||||
// hold more information in the future. Current values are just
|
||||
// "1" or "p" for present (which was written prior to
|
||||
// f5ba424cd44619d9b9be88b8593d69a7ba96db26).
|
||||
pwallet->LoadAddressPreviouslySpent(dest);
|
||||
} else if (strKey.compare(0, 2, "rr") == 0) {
|
||||
// Load "rr##" keys where ## is a decimal number, and strValue
|
||||
// is a serialized RecentRequestEntry object.
|
||||
pwallet->LoadAddressReceiveRequest(dest, strKey.substr(2), strValue);
|
||||
}
|
||||
} else if (strType == DBKeys::HDCHAIN) {
|
||||
CHDChain chain;
|
||||
ssValue >> chain;
|
||||
@ -1088,16 +1101,28 @@ void MaybeCompactWalletDB(WalletContext& context)
|
||||
fOneThread = false;
|
||||
}
|
||||
|
||||
bool WalletBatch::WriteDestData(const std::string &address, const std::string &key, const std::string &value)
|
||||
bool WalletBatch::WriteAddressPreviouslySpent(const CTxDestination& dest, bool previously_spent)
|
||||
{
|
||||
return WriteIC(std::make_pair(DBKeys::DESTDATA, std::make_pair(address, key)), value);
|
||||
auto key{std::make_pair(DBKeys::DESTDATA, std::make_pair(EncodeDestination(dest), std::string("used")))};
|
||||
return previously_spent ? WriteIC(key, std::string("1")) : EraseIC(key);
|
||||
}
|
||||
|
||||
bool WalletBatch::EraseDestData(const std::string &address, const std::string &key)
|
||||
bool WalletBatch::WriteAddressReceiveRequest(const CTxDestination& dest, const std::string& id, const std::string& receive_request)
|
||||
{
|
||||
return EraseIC(std::make_pair(DBKeys::DESTDATA, std::make_pair(address, key)));
|
||||
return WriteIC(std::make_pair(DBKeys::DESTDATA, std::make_pair(EncodeDestination(dest), "rr" + id)), receive_request);
|
||||
}
|
||||
|
||||
bool WalletBatch::EraseAddressReceiveRequest(const CTxDestination& dest, const std::string& id)
|
||||
{
|
||||
return EraseIC(std::make_pair(DBKeys::DESTDATA, std::make_pair(EncodeDestination(dest), "rr" + id)));
|
||||
}
|
||||
|
||||
bool WalletBatch::EraseAddressData(const CTxDestination& dest)
|
||||
{
|
||||
DataStream prefix;
|
||||
prefix << DBKeys::DESTDATA << EncodeDestination(dest);
|
||||
return m_batch->ErasePrefix(prefix);
|
||||
}
|
||||
|
||||
bool WalletBatch::WriteHDChain(const CHDChain& chain)
|
||||
{
|
||||
|
@ -7,6 +7,7 @@
|
||||
#define BITCOIN_WALLET_WALLETDB_H
|
||||
|
||||
#include <script/sign.h>
|
||||
#include <script/standard.h>
|
||||
#include <wallet/db.h>
|
||||
#include <wallet/walletutil.h>
|
||||
#include <key.h>
|
||||
@ -264,10 +265,10 @@ public:
|
||||
bool WriteLockedUTXO(const COutPoint& output);
|
||||
bool EraseLockedUTXO(const COutPoint& output);
|
||||
|
||||
/// Write destination data key,value tuple to database
|
||||
bool WriteDestData(const std::string &address, const std::string &key, const std::string &value);
|
||||
/// Erase destination data tuple from wallet database
|
||||
bool EraseDestData(const std::string &address, const std::string &key);
|
||||
bool WriteAddressPreviouslySpent(const CTxDestination& dest, bool previously_spent);
|
||||
bool WriteAddressReceiveRequest(const CTxDestination& dest, const std::string& id, const std::string& receive_request);
|
||||
bool EraseAddressReceiveRequest(const CTxDestination& dest, const std::string& id);
|
||||
bool EraseAddressData(const CTxDestination& dest);
|
||||
|
||||
bool WriteActiveScriptPubKeyMan(uint8_t type, const uint256& id, bool internal);
|
||||
bool EraseActiveScriptPubKeyMan(uint8_t type, bool internal);
|
||||
|
Loading…
x
Reference in New Issue
Block a user