Bitcoin Core 28.99.0
P2P Digital Currency
addresstablemodel.cpp
Go to the documentation of this file.
1// Copyright (c) 2011-2022 The Bitcoin Core developers
2// Distributed under the MIT software license, see the accompanying
3// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
6
7#include <qt/guiutil.h>
8#include <qt/walletmodel.h>
9
10#include <key_io.h>
11#include <wallet/types.h>
12#include <wallet/wallet.h>
13
14#include <algorithm>
15
16#include <QFont>
17#include <QDebug>
18
19const QString AddressTableModel::Send = "S";
20const QString AddressTableModel::Receive = "R";
21
23{
24 enum Type {
27 Hidden /* QSortFilterProxyModel will filter these out */
28 };
29
31 QString label;
32 QString address;
33
34 AddressTableEntry() = default;
35 AddressTableEntry(Type _type, const QString &_label, const QString &_address):
36 type(_type), label(_label), address(_address) {}
37};
38
40{
41 bool operator()(const AddressTableEntry &a, const AddressTableEntry &b) const
42 {
43 return a.address < b.address;
44 }
45 bool operator()(const AddressTableEntry &a, const QString &b) const
46 {
47 return a.address < b;
48 }
49 bool operator()(const QString &a, const AddressTableEntry &b) const
50 {
51 return a < b.address;
52 }
53};
54
55/* Determine address type from address purpose */
57{
58 // "refund" addresses aren't shown, and change addresses aren't returned by getAddresses at all.
59 switch (purpose) {
63 } // no default case, so the compiler can warn about missing cases
64 assert(false);
65}
66
67// Private implementation
69{
70public:
71 QList<AddressTableEntry> cachedAddressTable;
73
75 parent(_parent) {}
76
77 void refreshAddressTable(interfaces::Wallet& wallet, bool pk_hash_only = false)
78 {
79 cachedAddressTable.clear();
80 {
81 for (const auto& address : wallet.getAddresses())
82 {
83 if (pk_hash_only && !std::holds_alternative<PKHash>(address.dest)) {
84 continue;
85 }
87 address.purpose, address.is_mine);
88 cachedAddressTable.append(AddressTableEntry(addressType,
89 QString::fromStdString(address.name),
90 QString::fromStdString(EncodeDestination(address.dest))));
91 }
92 }
93 // std::lower_bound() and std::upper_bound() require our cachedAddressTable list to be sorted in asc order
94 // Even though the map is already sorted this re-sorting step is needed because the originating map
95 // is sorted by binary address, not by base58() address.
97 }
98
99 void updateEntry(const QString &address, const QString &label, bool isMine, wallet::AddressPurpose purpose, int status)
100 {
101 // Find address / label in model
102 QList<AddressTableEntry>::iterator lower = std::lower_bound(
104 QList<AddressTableEntry>::iterator upper = std::upper_bound(
106 int lowerIndex = (lower - cachedAddressTable.begin());
107 int upperIndex = (upper - cachedAddressTable.begin());
108 bool inModel = (lower != upper);
109 AddressTableEntry::Type newEntryType = translateTransactionType(purpose, isMine);
110
111 switch(status)
112 {
113 case CT_NEW:
114 if(inModel)
115 {
116 qWarning() << "AddressTablePriv::updateEntry: Warning: Got CT_NEW, but entry is already in model";
117 break;
118 }
119 parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex);
120 cachedAddressTable.insert(lowerIndex, AddressTableEntry(newEntryType, label, address));
121 parent->endInsertRows();
122 break;
123 case CT_UPDATED:
124 if(!inModel)
125 {
126 qWarning() << "AddressTablePriv::updateEntry: Warning: Got CT_UPDATED, but entry is not in model";
127 break;
128 }
129 lower->type = newEntryType;
130 lower->label = label;
131 parent->emitDataChanged(lowerIndex);
132 break;
133 case CT_DELETED:
134 if(!inModel)
135 {
136 qWarning() << "AddressTablePriv::updateEntry: Warning: Got CT_DELETED, but entry is not in model";
137 break;
138 }
139 parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1);
140 cachedAddressTable.erase(lower, upper);
141 parent->endRemoveRows();
142 break;
143 }
144 }
145
146 int size()
147 {
148 return cachedAddressTable.size();
149 }
150
152 {
153 if(idx >= 0 && idx < cachedAddressTable.size())
154 {
155 return &cachedAddressTable[idx];
156 }
157 else
158 {
159 return nullptr;
160 }
161 }
162};
163
165 QAbstractTableModel(parent), walletModel(parent)
166{
167 columns << tr("Label") << tr("Address");
168 priv = new AddressTablePriv(this);
169 priv->refreshAddressTable(parent->wallet(), pk_hash_only);
170}
171
173{
174 delete priv;
175}
176
177int AddressTableModel::rowCount(const QModelIndex &parent) const
178{
179 if (parent.isValid()) {
180 return 0;
181 }
182 return priv->size();
183}
184
185int AddressTableModel::columnCount(const QModelIndex &parent) const
186{
187 if (parent.isValid()) {
188 return 0;
189 }
190 return columns.length();
191}
192
193QVariant AddressTableModel::data(const QModelIndex &index, int role) const
194{
195 if(!index.isValid())
196 return QVariant();
197
198 AddressTableEntry *rec = static_cast<AddressTableEntry*>(index.internalPointer());
199
200 const auto column = static_cast<ColumnIndex>(index.column());
201 if (role == Qt::DisplayRole || role == Qt::EditRole) {
202 switch (column) {
203 case Label:
204 if (rec->label.isEmpty() && role == Qt::DisplayRole) {
205 return tr("(no label)");
206 } else {
207 return rec->label;
208 }
209 case Address:
210 return rec->address;
211 } // no default case, so the compiler can warn about missing cases
212 assert(false);
213 } else if (role == Qt::FontRole) {
214 switch (column) {
215 case Label:
216 return QFont();
217 case Address:
219 } // no default case, so the compiler can warn about missing cases
220 assert(false);
221 } else if (role == TypeRole) {
222 switch(rec->type)
223 {
225 return Send;
227 return Receive;
229 return {};
230 } // no default case, so the compiler can warn about missing cases
231 assert(false);
232 }
233 return QVariant();
234}
235
236bool AddressTableModel::setData(const QModelIndex &index, const QVariant &value, int role)
237{
238 if(!index.isValid())
239 return false;
240 AddressTableEntry *rec = static_cast<AddressTableEntry*>(index.internalPointer());
242 editStatus = OK;
243
244 if(role == Qt::EditRole)
245 {
246 CTxDestination curAddress = DecodeDestination(rec->address.toStdString());
247 if(index.column() == Label)
248 {
249 // Do nothing, if old label == new label
250 if(rec->label == value.toString())
251 {
253 return false;
254 }
255 walletModel->wallet().setAddressBook(curAddress, value.toString().toStdString(), purpose);
256 } else if(index.column() == Address) {
257 CTxDestination newAddress = DecodeDestination(value.toString().toStdString());
258 // Refuse to set invalid address, set error status and return false
259 if(std::get_if<CNoDestination>(&newAddress))
260 {
262 return false;
263 }
264 // Do nothing, if old address == new address
265 else if(newAddress == curAddress)
266 {
268 return false;
269 }
270 // Check for duplicate addresses to prevent accidental deletion of addresses, if you try
271 // to paste an existing address over another address (with a different label)
273 newAddress, /* name= */ nullptr, /* is_mine= */ nullptr, /* purpose= */ nullptr))
274 {
276 return false;
277 }
278 // Double-check that we're not overwriting a receiving address
279 else if(rec->type == AddressTableEntry::Sending)
280 {
281 // Remove old entry
282 walletModel->wallet().delAddressBook(curAddress);
283 // Add new entry with new address
284 walletModel->wallet().setAddressBook(newAddress, value.toString().toStdString(), purpose);
285 }
286 }
287 return true;
288 }
289 return false;
290}
291
292QVariant AddressTableModel::headerData(int section, Qt::Orientation orientation, int role) const
293{
294 if(orientation == Qt::Horizontal)
295 {
296 if(role == Qt::DisplayRole && section < columns.size())
297 {
298 return columns[section];
299 }
300 }
301 return QVariant();
302}
303
304Qt::ItemFlags AddressTableModel::flags(const QModelIndex &index) const
305{
306 if (!index.isValid()) return Qt::NoItemFlags;
307
308 AddressTableEntry *rec = static_cast<AddressTableEntry*>(index.internalPointer());
309
310 Qt::ItemFlags retval = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
311 // Can edit address and label for sending addresses,
312 // and only label for receiving addresses.
313 if(rec->type == AddressTableEntry::Sending ||
314 (rec->type == AddressTableEntry::Receiving && index.column()==Label))
315 {
316 retval |= Qt::ItemIsEditable;
317 }
318 return retval;
319}
320
321QModelIndex AddressTableModel::index(int row, int column, const QModelIndex &parent) const
322{
323 Q_UNUSED(parent);
325 if(data)
326 {
327 return createIndex(row, column, priv->index(row));
328 }
329 else
330 {
331 return QModelIndex();
332 }
333}
334
335void AddressTableModel::updateEntry(const QString &address,
336 const QString &label, bool isMine, wallet::AddressPurpose purpose, int status)
337{
338 // Update address book model from Bitcoin core
339 priv->updateEntry(address, label, isMine, purpose, status);
340}
341
342QString AddressTableModel::addRow(const QString &type, const QString &label, const QString &address, const OutputType address_type)
343{
344 std::string strLabel = label.toStdString();
345 std::string strAddress = address.toStdString();
346
347 editStatus = OK;
348
349 if(type == Send)
350 {
351 if(!walletModel->validateAddress(address))
352 {
354 return QString();
355 }
356 // Check for duplicate addresses
357 {
359 DecodeDestination(strAddress), /* name= */ nullptr, /* is_mine= */ nullptr, /* purpose= */ nullptr))
360 {
362 return QString();
363 }
364 }
365
366 // Add entry
368 }
369 else if(type == Receive)
370 {
371 // Generate a new address to associate with given label
372 if (auto dest{walletModel->wallet().getNewDestination(address_type, strLabel)}) {
373 strAddress = EncodeDestination(*dest);
374 } else {
376 if (!ctx.isValid()) {
377 // Unlock wallet failed or was cancelled
379 return QString();
380 }
381 if (auto dest_retry{walletModel->wallet().getNewDestination(address_type, strLabel)}) {
382 strAddress = EncodeDestination(*dest_retry);
383 } else {
385 return QString();
386 }
387 }
388 }
389 else
390 {
391 return QString();
392 }
393 return QString::fromStdString(strAddress);
394}
395
396bool AddressTableModel::removeRows(int row, int count, const QModelIndex &parent)
397{
398 Q_UNUSED(parent);
399 AddressTableEntry *rec = priv->index(row);
400 if(count != 1 || !rec || rec->type == AddressTableEntry::Receiving)
401 {
402 // Can only remove one row at a time, and cannot remove rows not in model.
403 // Also refuse to remove receiving addresses.
404 return false;
405 }
407 return true;
408}
409
410QString AddressTableModel::labelForAddress(const QString &address) const
411{
412 std::string name;
413 if (getAddressData(address, &name, /* purpose= */ nullptr)) {
414 return QString::fromStdString(name);
415 }
416 return QString();
417}
418
419std::optional<wallet::AddressPurpose> AddressTableModel::purposeForAddress(const QString &address) const
420{
422 if (getAddressData(address, /* name= */ nullptr, &purpose)) {
423 return purpose;
424 }
425 return std::nullopt;
426}
427
428bool AddressTableModel::getAddressData(const QString &address,
429 std::string* name,
430 wallet::AddressPurpose* purpose) const {
431 CTxDestination destination = DecodeDestination(address.toStdString());
432 return walletModel->wallet().getAddress(destination, name, /* is_mine= */ nullptr, purpose);
433}
434
435int AddressTableModel::lookupAddress(const QString &address) const
436{
437 QModelIndexList lst = match(index(0, Address, QModelIndex()),
438 Qt::EditRole, address, 1, Qt::MatchExactly);
439 if(lst.isEmpty())
440 {
441 return -1;
442 }
443 else
444 {
445 return lst.at(0).row();
446 }
447}
448
450
452{
453 Q_EMIT dataChanged(index(idx, 0, QModelIndex()), index(idx, columns.length()-1, QModelIndex()));
454}
455
constexpr AddressTableEntry::Type translateTransactionType(wallet::AddressPurpose purpose, bool isMine)
std::variant< CNoDestination, PubKeyDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessV1Taproot, PayToAnchor, WitnessUnknown > CTxDestination
A txout script categorized into standard templates.
Definition: addresstype.h:140
Qt model of the address book in the core.
bool removeRows(int row, int count, const QModelIndex &parent=QModelIndex()) override
@ TypeRole
Type of address (Send or Receive)
int lookupAddress(const QString &address) const
AddressTablePriv * priv
OutputType GetDefaultAddressType() const
@ WALLET_UNLOCK_FAILURE
Wallet could not be unlocked to create new receiving address.
@ NO_CHANGES
No changes were made during edit operation.
@ INVALID_ADDRESS
Unparseable address.
@ KEY_GENERATION_FAILURE
Generating a new public key for a receiving address failed.
@ OK
Everything ok.
@ DUPLICATE_ADDRESS
Address already in address book.
void emitDataChanged(int index)
Notify listeners that data changed.
@ Address
Bitcoin address.
@ Label
User specified label.
QVariant data(const QModelIndex &index, int role) const override
QModelIndex index(int row, int column, const QModelIndex &parent) const override
std::optional< wallet::AddressPurpose > purposeForAddress(const QString &address) const
Look up purpose for address in address book, if not found return empty string.
AddressTableModel(WalletModel *parent=nullptr, bool pk_hash_only=false)
bool getAddressData(const QString &address, std::string *name, wallet::AddressPurpose *purpose) const
Look up address book data given an address string.
int columnCount(const QModelIndex &parent) const override
bool setData(const QModelIndex &index, const QVariant &value, int role) override
static const QString Send
Specifies send address.
void updateEntry(const QString &address, const QString &label, bool isMine, wallet::AddressPurpose purpose, int status)
QString addRow(const QString &type, const QString &label, const QString &address, const OutputType address_type)
friend class AddressTablePriv
QVariant headerData(int section, Qt::Orientation orientation, int role) const override
QString GetWalletDisplayName() const
Qt::ItemFlags flags(const QModelIndex &index) const override
static const QString Receive
Specifies receive address.
int rowCount(const QModelIndex &parent) const override
WalletModel *const walletModel
QString labelForAddress(const QString &address) const
Look up label for address in address book, if not found return empty string.
QList< AddressTableEntry > cachedAddressTable
void updateEntry(const QString &address, const QString &label, bool isMine, wallet::AddressPurpose purpose, int status)
AddressTablePriv(AddressTableModel *_parent)
AddressTableEntry * index(int idx)
void refreshAddressTable(interfaces::Wallet &wallet, bool pk_hash_only=false)
AddressTableModel * parent
Interface to Bitcoin wallet from Qt view code.
Definition: walletmodel.h:48
bool validateAddress(const QString &address) const
interfaces::Wallet & wallet() const
Definition: walletmodel.h:138
QString getDisplayName() const
UnlockContext requestUnlock()
Interface for accessing a wallet.
Definition: wallet.h:66
virtual bool getAddress(const CTxDestination &dest, std::string *name, wallet::isminetype *is_mine, wallet::AddressPurpose *purpose)=0
Look up address in wallet, return whether exists.
virtual bool setAddressBook(const CTxDestination &dest, const std::string &name, const std::optional< wallet::AddressPurpose > &purpose)=0
Add or update address.
virtual OutputType getDefaultAddressType()=0
virtual bool delAddressBook(const CTxDestination &dest)=0
CTxDestination DecodeDestination(const std::string &str, std::string &error_msg, std::vector< int > *error_locations)
Definition: key_io.cpp:299
std::string EncodeDestination(const CTxDestination &dest)
Definition: key_io.cpp:294
QFont fixedPitchFont(bool use_embedded_font)
Definition: guiutil.cpp:100
AddressPurpose
Address purpose field that has been been stored with wallet sending and receiving addresses since BIP...
Definition: types.h:61
@ REFUND
Never set in current code may be present in older wallet databases.
CTxDestination getNewDestination(CWallet &w, OutputType output_type)
Returns a new destination, of an specific type, from the wallet.
Definition: util.cpp:92
OutputType
Definition: outputtype.h:17
const char * name
Definition: rest.cpp:49
Type type
QString label
AddressTableEntry()=default
AddressTableEntry(Type _type, const QString &_label, const QString &_address)
QString address
Type
@ Hidden
@ Sending
@ Receiving
bool operator()(const AddressTableEntry &a, const AddressTableEntry &b) const
bool operator()(const QString &a, const AddressTableEntry &b) const
bool operator()(const AddressTableEntry &a, const QString &b) const
static int count
@ CT_UPDATED
@ CT_DELETED
@ CT_NEW
assert(!tx.IsCoinBase())
is a home for public enum and struct type definitions that are used by internally by wallet code,...