Skip to content

Commit e83babe

Browse files
achow101ryanofsky
andcommitted
wallet: Replace use of purpose strings with an enum
Instead of storing and passing around fixed strings for the purpose of an address, use an enum. This also rationalizes the CAddressBookData struct, documenting all fields and making them public, and simplifying the representation to avoid bugs like bitcoin#26761 (comment) and make it not possible to invalid address data like change addresses with labels. Co-authored-by: Ryan Ofsky <[email protected]>
1 parent 2f80005 commit e83babe

16 files changed

+177
-119
lines changed

src/interfaces/wallet.h

+7-6
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ struct bilingual_str;
3535
namespace wallet {
3636
class CCoinControl;
3737
class CWallet;
38+
enum class AddressPurpose;
3839
enum isminetype : unsigned int;
3940
struct CRecipient;
4041
struct WalletContext;
@@ -103,7 +104,7 @@ class Wallet
103104
virtual bool haveWatchOnly() = 0;
104105

105106
//! Add or update address.
106-
virtual bool setAddressBook(const CTxDestination& dest, const std::string& name, const std::string& purpose) = 0;
107+
virtual bool setAddressBook(const CTxDestination& dest, const std::string& name, const std::optional<wallet::AddressPurpose>& purpose) = 0;
107108

108109
// Remove address.
109110
virtual bool delAddressBook(const CTxDestination& dest) = 0;
@@ -112,7 +113,7 @@ class Wallet
112113
virtual bool getAddress(const CTxDestination& dest,
113114
std::string* name,
114115
wallet::isminetype* is_mine,
115-
std::string* purpose) = 0;
116+
wallet::AddressPurpose* purpose) = 0;
116117

117118
//! Get wallet address list.
118119
virtual std::vector<WalletAddress> getAddresses() const = 0;
@@ -293,7 +294,7 @@ class Wallet
293294
using AddressBookChangedFn = std::function<void(const CTxDestination& address,
294295
const std::string& label,
295296
bool is_mine,
296-
const std::string& purpose,
297+
wallet::AddressPurpose purpose,
297298
ChangeType status)>;
298299
virtual std::unique_ptr<Handler> handleAddressBookChanged(AddressBookChangedFn fn) = 0;
299300

@@ -352,11 +353,11 @@ struct WalletAddress
352353
{
353354
CTxDestination dest;
354355
wallet::isminetype is_mine;
356+
wallet::AddressPurpose purpose;
355357
std::string name;
356-
std::string purpose;
357358

358-
WalletAddress(CTxDestination dest, wallet::isminetype is_mine, std::string name, std::string purpose)
359-
: dest(std::move(dest)), is_mine(is_mine), name(std::move(name)), purpose(std::move(purpose))
359+
WalletAddress(CTxDestination dest, wallet::isminetype is_mine, wallet::AddressPurpose purpose, std::string name)
360+
: dest(std::move(dest)), is_mine(is_mine), purpose(std::move(purpose)), name(std::move(name))
360361
{
361362
}
362363
};

src/qt/addresstablemodel.cpp

+21-21
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <qt/walletmodel.h>
99

1010
#include <key_io.h>
11+
#include <wallet/types.h>
1112
#include <wallet/wallet.h>
1213

1314
#include <algorithm>
@@ -52,17 +53,16 @@ struct AddressTableEntryLessThan
5253
};
5354

5455
/* Determine address type from address purpose */
55-
static AddressTableEntry::Type translateTransactionType(const QString &strPurpose, bool isMine)
56+
static AddressTableEntry::Type translateTransactionType(wallet::AddressPurpose purpose, bool isMine)
5657
{
57-
AddressTableEntry::Type addressType = AddressTableEntry::Hidden;
5858
// "refund" addresses aren't shown, and change addresses aren't returned by getAddresses at all.
59-
if (strPurpose == "send")
60-
addressType = AddressTableEntry::Sending;
61-
else if (strPurpose == "receive")
62-
addressType = AddressTableEntry::Receiving;
63-
else if (strPurpose == "unknown" || strPurpose == "") // if purpose not set, guess
64-
addressType = (isMine ? AddressTableEntry::Receiving : AddressTableEntry::Sending);
65-
return addressType;
59+
switch (purpose) {
60+
case wallet::AddressPurpose::SEND: return AddressTableEntry::Sending;
61+
case wallet::AddressPurpose::RECEIVE: return AddressTableEntry::Receiving;
62+
case wallet::AddressPurpose::REFUND: return AddressTableEntry::Hidden;
63+
// No default case to allow for compiler to warn
64+
}
65+
assert(false);
6666
}
6767

6868
// Private implementation
@@ -85,7 +85,7 @@ class AddressTablePriv
8585
continue;
8686
}
8787
AddressTableEntry::Type addressType = translateTransactionType(
88-
QString::fromStdString(address.purpose), address.is_mine);
88+
address.purpose, address.is_mine);
8989
cachedAddressTable.append(AddressTableEntry(addressType,
9090
QString::fromStdString(address.name),
9191
QString::fromStdString(EncodeDestination(address.dest))));
@@ -97,7 +97,7 @@ class AddressTablePriv
9797
std::sort(cachedAddressTable.begin(), cachedAddressTable.end(), AddressTableEntryLessThan());
9898
}
9999

100-
void updateEntry(const QString &address, const QString &label, bool isMine, const QString &purpose, int status)
100+
void updateEntry(const QString &address, const QString &label, bool isMine, wallet::AddressPurpose purpose, int status)
101101
{
102102
// Find address / label in model
103103
QList<AddressTableEntry>::iterator lower = std::lower_bound(
@@ -239,7 +239,7 @@ bool AddressTableModel::setData(const QModelIndex &index, const QVariant &value,
239239
if(!index.isValid())
240240
return false;
241241
AddressTableEntry *rec = static_cast<AddressTableEntry*>(index.internalPointer());
242-
std::string strPurpose = (rec->type == AddressTableEntry::Sending ? "send" : "receive");
242+
wallet::AddressPurpose purpose = rec->type == AddressTableEntry::Sending ? wallet::AddressPurpose::SEND : wallet::AddressPurpose::RECEIVE;
243243
editStatus = OK;
244244

245245
if(role == Qt::EditRole)
@@ -253,7 +253,7 @@ bool AddressTableModel::setData(const QModelIndex &index, const QVariant &value,
253253
editStatus = NO_CHANGES;
254254
return false;
255255
}
256-
walletModel->wallet().setAddressBook(curAddress, value.toString().toStdString(), strPurpose);
256+
walletModel->wallet().setAddressBook(curAddress, value.toString().toStdString(), purpose);
257257
} else if(index.column() == Address) {
258258
CTxDestination newAddress = DecodeDestination(value.toString().toStdString());
259259
// Refuse to set invalid address, set error status and return false
@@ -282,7 +282,7 @@ bool AddressTableModel::setData(const QModelIndex &index, const QVariant &value,
282282
// Remove old entry
283283
walletModel->wallet().delAddressBook(curAddress);
284284
// Add new entry with new address
285-
walletModel->wallet().setAddressBook(newAddress, value.toString().toStdString(), strPurpose);
285+
walletModel->wallet().setAddressBook(newAddress, value.toString().toStdString(), purpose);
286286
}
287287
}
288288
return true;
@@ -334,7 +334,7 @@ QModelIndex AddressTableModel::index(int row, int column, const QModelIndex &par
334334
}
335335

336336
void AddressTableModel::updateEntry(const QString &address,
337-
const QString &label, bool isMine, const QString &purpose, int status)
337+
const QString &label, bool isMine, wallet::AddressPurpose purpose, int status)
338338
{
339339
// Update address book model from Bitcoin core
340340
priv->updateEntry(address, label, isMine, purpose, status);
@@ -365,7 +365,7 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con
365365
}
366366

367367
// Add entry
368-
walletModel->wallet().setAddressBook(DecodeDestination(strAddress), strLabel, "send");
368+
walletModel->wallet().setAddressBook(DecodeDestination(strAddress), strLabel, wallet::AddressPurpose::SEND);
369369
}
370370
else if(type == Receive)
371371
{
@@ -416,18 +416,18 @@ QString AddressTableModel::labelForAddress(const QString &address) const
416416
return QString();
417417
}
418418

419-
QString AddressTableModel::purposeForAddress(const QString &address) const
419+
std::optional<wallet::AddressPurpose> AddressTableModel::purposeForAddress(const QString &address) const
420420
{
421-
std::string purpose;
421+
wallet::AddressPurpose purpose;
422422
if (getAddressData(address, /* name= */ nullptr, &purpose)) {
423-
return QString::fromStdString(purpose);
423+
return purpose;
424424
}
425-
return QString();
425+
return std::nullopt;
426426
}
427427

428428
bool AddressTableModel::getAddressData(const QString &address,
429429
std::string* name,
430-
std::string* purpose) const {
430+
wallet::AddressPurpose* purpose) const {
431431
CTxDestination destination = DecodeDestination(address.toStdString());
432432
return walletModel->wallet().getAddress(destination, name, /* is_mine= */ nullptr, purpose);
433433
}

src/qt/addresstablemodel.h

+8-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
#ifndef BITCOIN_QT_ADDRESSTABLEMODEL_H
66
#define BITCOIN_QT_ADDRESSTABLEMODEL_H
77

8+
#include <optional>
9+
810
#include <QAbstractTableModel>
911
#include <QStringList>
1012

@@ -16,6 +18,9 @@ class WalletModel;
1618
namespace interfaces {
1719
class Wallet;
1820
}
21+
namespace wallet {
22+
enum class AddressPurpose;
23+
} // namespace wallet
1924

2025
/**
2126
Qt model of the address book in the core. This allows views to access and modify the address book.
@@ -71,7 +76,7 @@ class AddressTableModel : public QAbstractTableModel
7176
QString labelForAddress(const QString &address) const;
7277

7378
/** Look up purpose for address in address book, if not found return empty string. */
74-
QString purposeForAddress(const QString &address) const;
79+
std::optional<wallet::AddressPurpose> purposeForAddress(const QString &address) const;
7580

7681
/* Look up row index of an address in the model.
7782
Return -1 if not found.
@@ -89,15 +94,15 @@ class AddressTableModel : public QAbstractTableModel
8994
EditStatus editStatus = OK;
9095

9196
/** Look up address book data given an address string. */
92-
bool getAddressData(const QString &address, std::string* name, std::string* purpose) const;
97+
bool getAddressData(const QString &address, std::string* name, wallet::AddressPurpose* purpose) const;
9398

9499
/** Notify listeners that data changed. */
95100
void emitDataChanged(int index);
96101

97102
public Q_SLOTS:
98103
/* Update address list from core.
99104
*/
100-
void updateEntry(const QString &address, const QString &label, bool isMine, const QString &purpose, int status);
105+
void updateEntry(const QString &address, const QString &label, bool isMine, wallet::AddressPurpose purpose, int status);
101106

102107
friend class AddressTablePriv;
103108
};

src/qt/editaddressdialog.cpp

+4-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
#include <qt/addresstablemodel.h>
99
#include <qt/guiutil.h>
1010

11+
#include <wallet/types.h>
12+
1113
#include <QDataWidgetMapper>
1214
#include <QMessageBox>
1315

@@ -137,9 +139,9 @@ QString EditAddressDialog::getDuplicateAddressWarning() const
137139
{
138140
QString dup_address = ui->addressEdit->text();
139141
QString existing_label = model->labelForAddress(dup_address);
140-
QString existing_purpose = model->purposeForAddress(dup_address);
142+
auto existing_purpose = model->purposeForAddress(dup_address);
141143

142-
if (existing_purpose == "receive" &&
144+
if (existing_purpose == wallet::AddressPurpose::RECEIVE &&
143145
(mode == NewSendingAddress || mode == EditSendingAddress)) {
144146
return tr(
145147
"Address \"%1\" already exists as a receiving address with label "

src/qt/test/addressbooktests.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,8 @@ void TestAddAddressesToSendBook(interfaces::Node& node)
113113

114114
{
115115
LOCK(wallet->cs_wallet);
116-
wallet->SetAddressBook(r_key_dest, r_label.toStdString(), "receive");
117-
wallet->SetAddressBook(s_key_dest, s_label.toStdString(), "send");
116+
wallet->SetAddressBook(r_key_dest, r_label.toStdString(), wallet::AddressPurpose::RECEIVE);
117+
wallet->SetAddressBook(s_key_dest, s_label.toStdString(), wallet::AddressPurpose::SEND);
118118
}
119119

120120
auto check_addbook_size = [&wallet](int expected_size) {

src/qt/test/wallettests.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ std::shared_ptr<CWallet> SetupDescriptorsWallet(interfaces::Node& node, TestChai
221221
WalletDescriptor w_desc(std::move(desc), 0, 0, 1, 1);
222222
if (!wallet->AddWalletDescriptor(w_desc, provider, "", false)) assert(false);
223223
CTxDestination dest = GetDestinationForKey(test.coinbaseKey.GetPubKey(), wallet->m_default_address_type);
224-
wallet->SetAddressBook(dest, "", "receive");
224+
wallet->SetAddressBook(dest, "", wallet::AddressPurpose::RECEIVE);
225225
wallet->SetLastBlockProcessed(105, WITH_LOCK(node.context()->chainman->GetMutex(), return node.context()->chainman->ActiveChain().Tip()->GetBlockHash()));
226226
SyncUpWallet(wallet, node);
227227
wallet->SetBroadcastTransactions(true);

src/qt/walletmodel.cpp

+6-7
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ void WalletModel::updateTransaction()
138138
}
139139

140140
void WalletModel::updateAddressBook(const QString &address, const QString &label,
141-
bool isMine, const QString &purpose, int status)
141+
bool isMine, wallet::AddressPurpose purpose, int status)
142142
{
143143
if(addressTableModel)
144144
addressTableModel->updateEntry(address, label, isMine, purpose, status);
@@ -280,11 +280,11 @@ void WalletModel::sendCoins(WalletModelTransaction& transaction)
280280
if (!m_wallet->getAddress(
281281
dest, &name, /* is_mine= */ nullptr, /* purpose= */ nullptr))
282282
{
283-
m_wallet->setAddressBook(dest, strLabel, "send");
283+
m_wallet->setAddressBook(dest, strLabel, wallet::AddressPurpose::SEND);
284284
}
285285
else if (name != strLabel)
286286
{
287-
m_wallet->setAddressBook(dest, strLabel, ""); // "" means don't change purpose
287+
m_wallet->setAddressBook(dest, strLabel, {}); // {} means don't change purpose
288288
}
289289
}
290290
}
@@ -377,18 +377,17 @@ static void NotifyKeyStoreStatusChanged(WalletModel *walletmodel)
377377

378378
static void NotifyAddressBookChanged(WalletModel *walletmodel,
379379
const CTxDestination &address, const std::string &label, bool isMine,
380-
const std::string &purpose, ChangeType status)
380+
wallet::AddressPurpose purpose, ChangeType status)
381381
{
382382
QString strAddress = QString::fromStdString(EncodeDestination(address));
383383
QString strLabel = QString::fromStdString(label);
384-
QString strPurpose = QString::fromStdString(purpose);
385384

386-
qDebug() << "NotifyAddressBookChanged: " + strAddress + " " + strLabel + " isMine=" + QString::number(isMine) + " purpose=" + strPurpose + " status=" + QString::number(status);
385+
qDebug() << "NotifyAddressBookChanged: " + strAddress + " " + strLabel + " isMine=" + QString::number(isMine) + " purpose=" + QString::number(static_cast<uint8_t>(purpose)) + " status=" + QString::number(status);
387386
bool invoked = QMetaObject::invokeMethod(walletmodel, "updateAddressBook",
388387
Q_ARG(QString, strAddress),
389388
Q_ARG(QString, strLabel),
390389
Q_ARG(bool, isMine),
391-
Q_ARG(QString, strPurpose),
390+
Q_ARG(wallet::AddressPurpose, purpose),
392391
Q_ARG(int, status));
393392
assert(invoked);
394393
}

src/qt/walletmodel.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ public Q_SLOTS:
236236
/* New transaction, or transaction changed status */
237237
void updateTransaction();
238238
/* New, updated or removed address book entry */
239-
void updateAddressBook(const QString &address, const QString &label, bool isMine, const QString &purpose, int status);
239+
void updateAddressBook(const QString &address, const QString &label, bool isMine, wallet::AddressPurpose purpose, int status);
240240
/* Watch-only added */
241241
void updateWatchOnlyFlag(bool fHaveWatchonly);
242242
/* Current, immature or unconfirmed balance might have changed - emit 'balanceChanged' if so */

src/wallet/interfaces.cpp

+14-7
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ class WalletImpl : public Wallet
179179
}
180180
return false;
181181
};
182-
bool setAddressBook(const CTxDestination& dest, const std::string& name, const std::string& purpose) override
182+
bool setAddressBook(const CTxDestination& dest, const std::string& name, const std::optional<AddressPurpose>& purpose) override
183183
{
184184
return m_wallet->SetAddressBook(dest, name, purpose);
185185
}
@@ -190,29 +190,36 @@ class WalletImpl : public Wallet
190190
bool getAddress(const CTxDestination& dest,
191191
std::string* name,
192192
isminetype* is_mine,
193-
std::string* purpose) override
193+
AddressPurpose* purpose) override
194194
{
195195
LOCK(m_wallet->cs_wallet);
196196
const auto& entry = m_wallet->FindAddressBookEntry(dest, /*allow_change=*/false);
197197
if (!entry) return false; // addr not found
198198
if (name) {
199199
*name = entry->GetLabel();
200200
}
201+
std::optional<isminetype> dest_is_mine;
202+
if (is_mine || purpose) {
203+
dest_is_mine = m_wallet->IsMine(dest);
204+
}
201205
if (is_mine) {
202-
*is_mine = m_wallet->IsMine(dest);
206+
*is_mine = *dest_is_mine;
203207
}
204208
if (purpose) {
205-
*purpose = entry->purpose;
209+
// In very old wallets, address purpose may not be recorded so we derive it from IsMine
210+
*purpose = entry->purpose.value_or(*dest_is_mine ? AddressPurpose::RECEIVE : AddressPurpose::SEND);
206211
}
207212
return true;
208213
}
209214
std::vector<WalletAddress> getAddresses() const override
210215
{
211216
LOCK(m_wallet->cs_wallet);
212217
std::vector<WalletAddress> result;
213-
m_wallet->ForEachAddrBookEntry([&](const CTxDestination& dest, const std::string& label, const std::string& purpose, bool is_change) EXCLUSIVE_LOCKS_REQUIRED(m_wallet->cs_wallet) {
218+
m_wallet->ForEachAddrBookEntry([&](const CTxDestination& dest, const std::string& label, bool is_change, const std::optional<AddressPurpose>& purpose) EXCLUSIVE_LOCKS_REQUIRED(m_wallet->cs_wallet) {
214219
if (is_change) return;
215-
result.emplace_back(dest, m_wallet->IsMine(dest), label, purpose);
220+
isminetype is_mine = m_wallet->IsMine(dest);
221+
// In very old wallets, address purpose may not be recorded so we derive it from IsMine
222+
result.emplace_back(dest, is_mine, purpose.value_or(is_mine ? AddressPurpose::RECEIVE : AddressPurpose::SEND), label);
216223
});
217224
return result;
218225
}
@@ -519,7 +526,7 @@ class WalletImpl : public Wallet
519526
{
520527
return MakeSignalHandler(m_wallet->NotifyAddressBookChanged.connect(
521528
[fn](const CTxDestination& address, const std::string& label, bool is_mine,
522-
const std::string& purpose, ChangeType status) { fn(address, label, is_mine, purpose, status); }));
529+
AddressPurpose purpose, ChangeType status) { fn(address, label, is_mine, purpose, status); }));
523530
}
524531
std::unique_ptr<Handler> handleTransactionChanged(TransactionChangedFn fn) override
525532
{

0 commit comments

Comments
 (0)