mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-03-27 07:55:39 +01:00
Merge #18192: Bugfix: Wallet: Safely deal with change in the address book
b5795a7886Wallet: Add warning comments and assert to CWallet::DelAddressBook (Luke Dashjr)6d2905f57aWallet: Avoid unnecessary/redundant m_address_book lookups (Luke Dashjr)c751d886f4Wallet: Avoid treating change-in-the-addressbook as non-change everywhere (Luke Dashjr)8e64b8c84bWallet: New FindAddressBookEntry method to filter out change entries (and skip ->second everywhere) (Luke Dashjr)65b6bdc2b1Wallet: Add CAddressBookData::IsChange which returns true iff label has never been set (Luke Dashjr)144b2f85daWallet: Require usage of new CAddressBookData::setLabel to change label (Luke Dashjr)b86cd155f6scripted-diff: Wallet: Rename mapAddressBook to m_address_book (Luke Dashjr) Pull request description: In many places, our code assumes that presence in the address book indicates a non-change key, and absence of an entry in mapAddressBook indicates change. This no longer holds true after #13756 (first released in 0.19) since it added a "used" DestData populated even for change addresses. Only avoid-reuse wallets should be affected by this issue. Thankfully, populating DestData does not write a label to the database, so we can retroactively fix this (so long as the user didn't see the change address and manually assign it a real label). Fixing it is accomplished by: * Adding a new bool to CAddressBookData to track if the label has ever been assigned, either by loading one from the database, or by assigning one at runtime. * `CAddressBookData::IsChange` and `CWallet::FindAddressBookEntry` are new methods to assist in excluding change from code that doesn't expect to see them. * For safety in merging, `CAddressBookData::name` has been made read-only (the actual data is stored in `m_label`, a new private member, and can be changed only with `setLabel` which updates the `m_change` flag), and `mapAddressBook` has been renamed to `m_address_book` (to force old code to be rebased to compile). A final commit also does some minor optimisation, avoiding redundant lookups in `m_address_book` when we already have a pointer to the `CAddressBookData`. ACKs for top commit: ryanofsky: Code review ACKb5795a7886. Pretty clever and nicely implemented fix! jonatack: ACKb5795a7886nice improvements -- code review, built/ran tests rebased on current masterff53433fe4and tested manually with rpc/cli jnewbery: Good fix. utACKb5795a788. Tree-SHA512: 40525185a0bcc1723f602243c269499ec86ecb298fecb5ef24d626bbdd5e3efece86cdb1084ad7eebf7eeaf251db4a6e056bcd25bc8457b417fcbb53d032ebf0
This commit is contained in:
@@ -1242,8 +1242,9 @@ bool CWallet::IsChange(const CScript& script) const
|
||||
return true;
|
||||
|
||||
LOCK(cs_wallet);
|
||||
if (!mapAddressBook.count(address))
|
||||
if (!FindAddressBookEntry(address)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -3196,11 +3197,11 @@ bool CWallet::SetAddressBookWithDB(WalletBatch& batch, const CTxDestination& add
|
||||
bool fUpdated = false;
|
||||
{
|
||||
LOCK(cs_wallet);
|
||||
std::map<CTxDestination, CAddressBookData>::iterator mi = mapAddressBook.find(address);
|
||||
fUpdated = mi != mapAddressBook.end();
|
||||
mapAddressBook[address].name = strName;
|
||||
std::map<CTxDestination, CAddressBookData>::iterator mi = m_address_book.find(address);
|
||||
fUpdated = (mi != m_address_book.end() && !mi->second.IsChange());
|
||||
m_address_book[address].SetLabel(strName);
|
||||
if (!strPurpose.empty()) /* update purpose only if requested */
|
||||
mapAddressBook[address].purpose = strPurpose;
|
||||
m_address_book[address].purpose = strPurpose;
|
||||
}
|
||||
NotifyAddressBookChanged(this, address, strName, IsMine(address) != ISMINE_NO,
|
||||
strPurpose, (fUpdated ? CT_UPDATED : CT_NEW) );
|
||||
@@ -3217,16 +3218,21 @@ bool CWallet::SetAddressBook(const CTxDestination& address, const std::string& s
|
||||
|
||||
bool CWallet::DelAddressBook(const CTxDestination& address)
|
||||
{
|
||||
// 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?).
|
||||
assert(!IsMine(address));
|
||||
|
||||
{
|
||||
LOCK(cs_wallet);
|
||||
|
||||
// Delete destdata tuples associated with address
|
||||
std::string strAddress = EncodeDestination(address);
|
||||
for (const std::pair<const std::string, std::string> &item : mapAddressBook[address].destdata)
|
||||
for (const std::pair<const std::string, std::string> &item : m_address_book[address].destdata)
|
||||
{
|
||||
WalletBatch(*database).EraseDestData(strAddress, item.first);
|
||||
}
|
||||
mapAddressBook.erase(address);
|
||||
m_address_book.erase(address);
|
||||
}
|
||||
|
||||
NotifyAddressBookChanged(this, address, "", IsMine(address) != ISMINE_NO, "", CT_DELETED);
|
||||
@@ -3462,8 +3468,9 @@ std::set<CTxDestination> CWallet::GetLabelAddresses(const std::string& label) co
|
||||
{
|
||||
LOCK(cs_wallet);
|
||||
std::set<CTxDestination> result;
|
||||
for (const std::pair<const CTxDestination, CAddressBookData>& item : mapAddressBook)
|
||||
for (const std::pair<const CTxDestination, CAddressBookData>& item : m_address_book)
|
||||
{
|
||||
if (item.second.IsChange()) continue;
|
||||
const CTxDestination& address = item.first;
|
||||
const std::string& strName = item.second.name;
|
||||
if (strName == label)
|
||||
@@ -3666,26 +3673,26 @@ bool CWallet::AddDestData(WalletBatch& batch, const CTxDestination &dest, const
|
||||
if (boost::get<CNoDestination>(&dest))
|
||||
return false;
|
||||
|
||||
mapAddressBook[dest].destdata.insert(std::make_pair(key, value));
|
||||
m_address_book[dest].destdata.insert(std::make_pair(key, value));
|
||||
return batch.WriteDestData(EncodeDestination(dest), key, value);
|
||||
}
|
||||
|
||||
bool CWallet::EraseDestData(WalletBatch& batch, const CTxDestination &dest, const std::string &key)
|
||||
{
|
||||
if (!mapAddressBook[dest].destdata.erase(key))
|
||||
if (!m_address_book[dest].destdata.erase(key))
|
||||
return false;
|
||||
return batch.EraseDestData(EncodeDestination(dest), key);
|
||||
}
|
||||
|
||||
void CWallet::LoadDestData(const CTxDestination &dest, const std::string &key, const std::string &value)
|
||||
{
|
||||
mapAddressBook[dest].destdata.insert(std::make_pair(key, value));
|
||||
m_address_book[dest].destdata.insert(std::make_pair(key, value));
|
||||
}
|
||||
|
||||
bool CWallet::GetDestData(const CTxDestination &dest, const std::string &key, std::string *value) const
|
||||
{
|
||||
std::map<CTxDestination, CAddressBookData>::const_iterator i = mapAddressBook.find(dest);
|
||||
if(i != mapAddressBook.end())
|
||||
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())
|
||||
@@ -3701,7 +3708,7 @@ bool CWallet::GetDestData(const CTxDestination &dest, const std::string &key, st
|
||||
std::vector<std::string> CWallet::GetDestValues(const std::string& prefix) const
|
||||
{
|
||||
std::vector<std::string> values;
|
||||
for (const auto& address : mapAddressBook) {
|
||||
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);
|
||||
@@ -4103,12 +4110,22 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain,
|
||||
{
|
||||
walletInstance->WalletLogPrintf("setKeyPool.size() = %u\n", walletInstance->GetKeyPoolSize());
|
||||
walletInstance->WalletLogPrintf("mapWallet.size() = %u\n", walletInstance->mapWallet.size());
|
||||
walletInstance->WalletLogPrintf("mapAddressBook.size() = %u\n", walletInstance->mapAddressBook.size());
|
||||
walletInstance->WalletLogPrintf("m_address_book.size() = %u\n", walletInstance->m_address_book.size());
|
||||
}
|
||||
|
||||
return walletInstance;
|
||||
}
|
||||
|
||||
const CAddressBookData* CWallet::FindAddressBookEntry(const CTxDestination& dest, bool allow_change) const
|
||||
{
|
||||
const auto& address_book_it = m_address_book.find(dest);
|
||||
if (address_book_it == m_address_book.end()) return nullptr;
|
||||
if ((!allow_change) && address_book_it->second.IsChange()) {
|
||||
return nullptr;
|
||||
}
|
||||
return &address_book_it->second;
|
||||
}
|
||||
|
||||
void CWallet::postInitProcess()
|
||||
{
|
||||
auto locked_chain = chain().lock();
|
||||
|
||||
Reference in New Issue
Block a user