Skip to content

Commit

Permalink
wallet: Replace use of purpose strings with an enum
Browse files Browse the repository at this point in the history
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 <ryan@ofsky.org>
  • Loading branch information
achow101 and ryanofsky committed Mar 15, 2023
1 parent 2932d7b commit db521d3
Show file tree
Hide file tree
Showing 17 changed files with 183 additions and 115 deletions.
13 changes: 7 additions & 6 deletions src/interfaces/wallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ struct bilingual_str;
namespace wallet {
class CCoinControl;
class CWallet;
enum class AddressPurpose;
enum isminetype : unsigned int;
struct CRecipient;
struct WalletContext;
Expand Down Expand Up @@ -103,7 +104,7 @@ class Wallet
virtual bool haveWatchOnly() = 0;

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

// Remove address.
virtual bool delAddressBook(const CTxDestination& dest) = 0;
Expand All @@ -112,7 +113,7 @@ class Wallet
virtual bool getAddress(const CTxDestination& dest,
std::string* name,
wallet::isminetype* is_mine,
std::string* purpose) = 0;
wallet::AddressPurpose* purpose) = 0;

//! Get wallet address list.
virtual std::vector<WalletAddress> getAddresses() const = 0;
Expand Down Expand Up @@ -293,7 +294,7 @@ class Wallet
using AddressBookChangedFn = std::function<void(const CTxDestination& address,
const std::string& label,
bool is_mine,
const std::string& purpose,
wallet::AddressPurpose purpose,
ChangeType status)>;
virtual std::unique_ptr<Handler> handleAddressBookChanged(AddressBookChangedFn fn) = 0;

Expand Down Expand Up @@ -352,11 +353,11 @@ struct WalletAddress
{
CTxDestination dest;
wallet::isminetype is_mine;
wallet::AddressPurpose purpose;
std::string name;
std::string purpose;

WalletAddress(CTxDestination dest, wallet::isminetype is_mine, std::string name, std::string purpose)
: dest(std::move(dest)), is_mine(is_mine), name(std::move(name)), purpose(std::move(purpose))
WalletAddress(CTxDestination dest, wallet::isminetype is_mine, wallet::AddressPurpose purpose, std::string name)
: dest(std::move(dest)), is_mine(is_mine), purpose(std::move(purpose)), name(std::move(name))
{
}
};
Expand Down
34 changes: 18 additions & 16 deletions src/qt/addresstablemodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <qt/walletmodel.h>

#include <key_io.h>
#include <wallet/types.h>
#include <wallet/wallet.h>

#include <algorithm>
Expand Down Expand Up @@ -52,16 +53,17 @@ struct AddressTableEntryLessThan
};

/* Determine address type from address purpose */
static AddressTableEntry::Type translateTransactionType(const QString &strPurpose, bool isMine)
static AddressTableEntry::Type translateTransactionType(wallet::AddressPurpose purpose, bool isMine)
{
AddressTableEntry::Type addressType = AddressTableEntry::Hidden;
// "refund" addresses aren't shown, and change addresses aren't returned by getAddresses at all.
if (strPurpose == "send")
if (purpose == wallet::AddressPurpose::SEND) {
addressType = AddressTableEntry::Sending;
else if (strPurpose == "receive")
} else if (purpose == wallet::AddressPurpose::RECEIVE) {
addressType = AddressTableEntry::Receiving;
else if (strPurpose == "unknown" || strPurpose == "") // if purpose not set, guess
} else {
addressType = (isMine ? AddressTableEntry::Receiving : AddressTableEntry::Sending);
}
return addressType;
}

Expand All @@ -85,7 +87,7 @@ class AddressTablePriv
continue;
}
AddressTableEntry::Type addressType = translateTransactionType(
QString::fromStdString(address.purpose), address.is_mine);
address.purpose, address.is_mine);
cachedAddressTable.append(AddressTableEntry(addressType,
QString::fromStdString(address.name),
QString::fromStdString(EncodeDestination(address.dest))));
Expand All @@ -97,7 +99,7 @@ class AddressTablePriv
std::sort(cachedAddressTable.begin(), cachedAddressTable.end(), AddressTableEntryLessThan());
}

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

if(role == Qt::EditRole)
Expand All @@ -253,7 +255,7 @@ bool AddressTableModel::setData(const QModelIndex &index, const QVariant &value,
editStatus = NO_CHANGES;
return false;
}
walletModel->wallet().setAddressBook(curAddress, value.toString().toStdString(), strPurpose);
walletModel->wallet().setAddressBook(curAddress, value.toString().toStdString(), purpose);
} else if(index.column() == Address) {
CTxDestination newAddress = DecodeDestination(value.toString().toStdString());
// Refuse to set invalid address, set error status and return false
Expand Down Expand Up @@ -282,7 +284,7 @@ bool AddressTableModel::setData(const QModelIndex &index, const QVariant &value,
// Remove old entry
walletModel->wallet().delAddressBook(curAddress);
// Add new entry with new address
walletModel->wallet().setAddressBook(newAddress, value.toString().toStdString(), strPurpose);
walletModel->wallet().setAddressBook(newAddress, value.toString().toStdString(), purpose);
}
}
return true;
Expand Down Expand Up @@ -334,7 +336,7 @@ QModelIndex AddressTableModel::index(int row, int column, const QModelIndex &par
}

void AddressTableModel::updateEntry(const QString &address,
const QString &label, bool isMine, const QString &purpose, int status)
const QString &label, bool isMine, wallet::AddressPurpose purpose, int status)
{
// Update address book model from Bitcoin core
priv->updateEntry(address, label, isMine, purpose, status);
Expand Down Expand Up @@ -365,7 +367,7 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con
}

// Add entry
walletModel->wallet().setAddressBook(DecodeDestination(strAddress), strLabel, "send");
walletModel->wallet().setAddressBook(DecodeDestination(strAddress), strLabel, wallet::AddressPurpose::SEND);
}
else if(type == Receive)
{
Expand Down Expand Up @@ -416,18 +418,18 @@ QString AddressTableModel::labelForAddress(const QString &address) const
return QString();
}

QString AddressTableModel::purposeForAddress(const QString &address) const
std::optional<wallet::AddressPurpose> AddressTableModel::purposeForAddress(const QString &address) const
{
std::string purpose;
wallet::AddressPurpose purpose;
if (getAddressData(address, /* name= */ nullptr, &purpose)) {
return QString::fromStdString(purpose);
return purpose;
}
return QString();
return std::nullopt;
}

bool AddressTableModel::getAddressData(const QString &address,
std::string* name,
std::string* purpose) const {
wallet::AddressPurpose* purpose) const {
CTxDestination destination = DecodeDestination(address.toStdString());
return walletModel->wallet().getAddress(destination, name, /* is_mine= */ nullptr, purpose);
}
Expand Down
11 changes: 8 additions & 3 deletions src/qt/addresstablemodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#ifndef BITCOIN_QT_ADDRESSTABLEMODEL_H
#define BITCOIN_QT_ADDRESSTABLEMODEL_H

#include <optional>

#include <QAbstractTableModel>
#include <QStringList>

Expand All @@ -16,6 +18,9 @@ class WalletModel;
namespace interfaces {
class Wallet;
}
namespace wallet {
enum class AddressPurpose;
} // namespace wallet

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

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

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

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

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

public Q_SLOTS:
/* Update address list from core.
*/
void updateEntry(const QString &address, const QString &label, bool isMine, const QString &purpose, int status);
void updateEntry(const QString &address, const QString &label, bool isMine, wallet::AddressPurpose purpose, int status);

friend class AddressTablePriv;
};
Expand Down
6 changes: 4 additions & 2 deletions src/qt/editaddressdialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
#include <qt/addresstablemodel.h>
#include <qt/guiutil.h>

#include <wallet/types.h>

#include <QDataWidgetMapper>
#include <QMessageBox>

Expand Down Expand Up @@ -137,9 +139,9 @@ QString EditAddressDialog::getDuplicateAddressWarning() const
{
QString dup_address = ui->addressEdit->text();
QString existing_label = model->labelForAddress(dup_address);
QString existing_purpose = model->purposeForAddress(dup_address);
auto existing_purpose = model->purposeForAddress(dup_address);

if (existing_purpose == "receive" &&
if (existing_purpose == wallet::AddressPurpose::RECEIVE &&
(mode == NewSendingAddress || mode == EditSendingAddress)) {
return tr(
"Address \"%1\" already exists as a receiving address with label "
Expand Down
4 changes: 2 additions & 2 deletions src/qt/test/addressbooktests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ void TestAddAddressesToSendBook(interfaces::Node& node)

{
LOCK(wallet->cs_wallet);
wallet->SetAddressBook(r_key_dest, r_label.toStdString(), "receive");
wallet->SetAddressBook(s_key_dest, s_label.toStdString(), "send");
wallet->SetAddressBook(r_key_dest, r_label.toStdString(), wallet::AddressPurpose::RECEIVE);
wallet->SetAddressBook(s_key_dest, s_label.toStdString(), wallet::AddressPurpose::SEND);
}

auto check_addbook_size = [&wallet](int expected_size) {
Expand Down
2 changes: 1 addition & 1 deletion src/qt/test/wallettests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ void TestGUI(interfaces::Node& node)
WalletDescriptor w_desc(std::move(desc), 0, 0, 1, 1);
if (!wallet->AddWalletDescriptor(w_desc, provider, "", false)) assert(false);
CTxDestination dest = GetDestinationForKey(test.coinbaseKey.GetPubKey(), wallet->m_default_address_type);
wallet->SetAddressBook(dest, "", "receive");
wallet->SetAddressBook(dest, "", wallet::AddressPurpose::RECEIVE);
wallet->SetLastBlockProcessed(105, WITH_LOCK(node.context()->chainman->GetMutex(), return node.context()->chainman->ActiveChain().Tip()->GetBlockHash()));
}
{
Expand Down
13 changes: 6 additions & 7 deletions src/qt/walletmodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ void WalletModel::updateTransaction()
}

void WalletModel::updateAddressBook(const QString &address, const QString &label,
bool isMine, const QString &purpose, int status)
bool isMine, wallet::AddressPurpose purpose, int status)
{
if(addressTableModel)
addressTableModel->updateEntry(address, label, isMine, purpose, status);
Expand Down Expand Up @@ -280,11 +280,11 @@ void WalletModel::sendCoins(WalletModelTransaction& transaction)
if (!m_wallet->getAddress(
dest, &name, /* is_mine= */ nullptr, /* purpose= */ nullptr))
{
m_wallet->setAddressBook(dest, strLabel, "send");
m_wallet->setAddressBook(dest, strLabel, wallet::AddressPurpose::SEND);
}
else if (name != strLabel)
{
m_wallet->setAddressBook(dest, strLabel, ""); // "" means don't change purpose
m_wallet->setAddressBook(dest, strLabel, {}); // {} means don't change purpose
}
}
}
Expand Down Expand Up @@ -377,18 +377,17 @@ static void NotifyKeyStoreStatusChanged(WalletModel *walletmodel)

static void NotifyAddressBookChanged(WalletModel *walletmodel,
const CTxDestination &address, const std::string &label, bool isMine,
const std::string &purpose, ChangeType status)
wallet::AddressPurpose purpose, ChangeType status)
{
QString strAddress = QString::fromStdString(EncodeDestination(address));
QString strLabel = QString::fromStdString(label);
QString strPurpose = QString::fromStdString(purpose);

qDebug() << "NotifyAddressBookChanged: " + strAddress + " " + strLabel + " isMine=" + QString::number(isMine) + " purpose=" + strPurpose + " status=" + QString::number(status);
qDebug() << "NotifyAddressBookChanged: " + strAddress + " " + strLabel + " isMine=" + QString::number(isMine) + " purpose=" + QString::number(static_cast<uint8_t>(purpose)) + " status=" + QString::number(status);
bool invoked = QMetaObject::invokeMethod(walletmodel, "updateAddressBook",
Q_ARG(QString, strAddress),
Q_ARG(QString, strLabel),
Q_ARG(bool, isMine),
Q_ARG(QString, strPurpose),
Q_ARG(wallet::AddressPurpose, purpose),
Q_ARG(int, status));
assert(invoked);
}
Expand Down
2 changes: 1 addition & 1 deletion src/qt/walletmodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ public Q_SLOTS:
/* New transaction, or transaction changed status */
void updateTransaction();
/* New, updated or removed address book entry */
void updateAddressBook(const QString &address, const QString &label, bool isMine, const QString &purpose, int status);
void updateAddressBook(const QString &address, const QString &label, bool isMine, wallet::AddressPurpose purpose, int status);
/* Watch-only added */
void updateWatchOnlyFlag(bool fHaveWatchonly);
/* Current, immature or unconfirmed balance might have changed - emit 'balanceChanged' if so */
Expand Down
19 changes: 12 additions & 7 deletions src/wallet/interfaces.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ class WalletImpl : public Wallet
}
return false;
};
bool setAddressBook(const CTxDestination& dest, const std::string& name, const std::string& purpose) override
bool setAddressBook(const CTxDestination& dest, const std::string& name, const std::optional<AddressPurpose>& purpose) override
{
return m_wallet->SetAddressBook(dest, name, purpose);
}
Expand All @@ -189,29 +189,34 @@ class WalletImpl : public Wallet
bool getAddress(const CTxDestination& dest,
std::string* name,
isminetype* is_mine,
std::string* purpose) override
AddressPurpose* purpose) override
{
LOCK(m_wallet->cs_wallet);
const auto& entry = m_wallet->FindAddressBookEntry(dest, /*allow_change=*/false);
if (!entry) return false; // addr not found
if (name) {
*name = entry->GetLabel();
}
std::optional<isminetype> dest_is_mine;
if (is_mine || purpose) {
dest_is_mine = m_wallet->IsMine(dest);
}
if (is_mine) {
*is_mine = m_wallet->IsMine(dest);
*is_mine = *dest_is_mine;
}
if (purpose) {
*purpose = entry->purpose;
*purpose = entry->purpose ? *entry->purpose : *dest_is_mine ? AddressPurpose::RECEIVE : AddressPurpose::SEND;
}
return true;
}
std::vector<WalletAddress> getAddresses() const override
{
LOCK(m_wallet->cs_wallet);
std::vector<WalletAddress> result;
m_wallet->ForEachAddrBookEntry([&](const CTxDestination& dest, const std::string& label, const std::string& purpose, bool is_change) EXCLUSIVE_LOCKS_REQUIRED(m_wallet->cs_wallet) {
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) {
if (is_change) return;
result.emplace_back(dest, m_wallet->IsMine(dest), label, purpose);
isminetype is_mine = m_wallet->IsMine(dest);
result.emplace_back(dest, is_mine, purpose ? *purpose : is_mine ? AddressPurpose::RECEIVE : AddressPurpose::SEND, label);
});
return result;
}
Expand Down Expand Up @@ -501,7 +506,7 @@ class WalletImpl : public Wallet
{
return MakeSignalHandler(m_wallet->NotifyAddressBookChanged.connect(
[fn](const CTxDestination& address, const std::string& label, bool is_mine,
const std::string& purpose, ChangeType status) { fn(address, label, is_mine, purpose, status); }));
AddressPurpose purpose, ChangeType status) { fn(address, label, is_mine, purpose, status); }));
}
std::unique_ptr<Handler> handleTransactionChanged(TransactionChangedFn fn) override
{
Expand Down
Loading

0 comments on commit db521d3

Please sign in to comment.