Merge bitcoin/bitcoin#27224: refactor: Remove CAddressBookData::destdata

a5986e82dd refactor: Remove CAddressBookData::destdata (Ryan Ofsky)
5938ad0bdb 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 a5986e82dd
  furszy:
    Code ACK a5986e82

Tree-SHA512: 6bd3e402f1f60263fbd433760bdc29d04175ddaf8307207c4a67d59f6cffa732e176ba57886e02926f7a1615dce0ed9cda36c8cbc6430aa8e5b56934c23f3fe7
This commit is contained in:
Andrew Chow
2023-05-01 08:09:36 -04:00
11 changed files with 215 additions and 95 deletions

View File

@@ -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;
}