Bitcoin Core 22.99.0
P2P Digital Currency
addresstablemodel.cpp
Go to the documentation of this file.
1// Copyright (c) 2011-2020 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/wallet.h>
12
13#include <algorithm>
14
15#include <QFont>
16#include <QDebug>
17
18const QString AddressTableModel::Send = "S";
19const QString AddressTableModel::Receive = "R";
20
22{
23 enum Type {
26 Hidden /* QSortFilterProxyModel will filter these out */
27 };
28
30 QString label;
31 QString address;
32
34 AddressTableEntry(Type _type, const QString &_label, const QString &_address):
35 type(_type), label(_label), address(_address) {}
36};
37
39{
40 bool operator()(const AddressTableEntry &a, const AddressTableEntry &b) const
41 {
42 return a.address < b.address;
43 }
44 bool operator()(const AddressTableEntry &a, const QString &b) const
45 {
46 return a.address < b;
47 }
48 bool operator()(const QString &a, const AddressTableEntry &b) const
49 {
50 return a < b.address;
51 }
52};
53
54/* Determine address type from address purpose */
55static AddressTableEntry::Type translateTransactionType(const QString &strPurpose, bool isMine)
56{
58 // "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
65 return addressType;
66}
67
68// Private implementation
70{
71public:
72 QList<AddressTableEntry> cachedAddressTable;
74
76 parent(_parent) {}
77
78 void refreshAddressTable(interfaces::Wallet& wallet, bool pk_hash_only = false)
79 {
80 cachedAddressTable.clear();
81 {
82 for (const auto& address : wallet.getAddresses())
83 {
84 if (pk_hash_only && !std::holds_alternative<PKHash>(address.dest)) {
85 continue;
86 }
88 QString::fromStdString(address.purpose), address.is_mine);
89 cachedAddressTable.append(AddressTableEntry(addressType,
90 QString::fromStdString(address.name),
91 QString::fromStdString(EncodeDestination(address.dest))));
92 }
93 }
94 // std::lower_bound() and std::upper_bound() require our cachedAddressTable list to be sorted in asc order
95 // Even though the map is already sorted this re-sorting step is needed because the originating map
96 // is sorted by binary address, not by base58() address.
98 }
99
100 void updateEntry(const QString &address, const QString &label, bool isMine, const QString &purpose, int status)
101 {
102 // Find address / label in model
103 QList<AddressTableEntry>::iterator lower = std::lower_bound(
105 QList<AddressTableEntry>::iterator upper = std::upper_bound(
107 int lowerIndex = (lower - cachedAddressTable.begin());
108 int upperIndex = (upper - cachedAddressTable.begin());
109 bool inModel = (lower != upper);
110 AddressTableEntry::Type newEntryType = translateTransactionType(purpose, isMine);
111
112 switch(status)
113 {
114 case CT_NEW:
115 if(inModel)
116 {
117 qWarning() << "AddressTablePriv::updateEntry: Warning: Got CT_NEW, but entry is already in model";
118 break;
119 }
120 parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex);
121 cachedAddressTable.insert(lowerIndex, AddressTableEntry(newEntryType, label, address));
122 parent->endInsertRows();
123 break;
124 case CT_UPDATED:
125 if(!inModel)
126 {
127 qWarning() << "AddressTablePriv::updateEntry: Warning: Got CT_UPDATED, but entry is not in model";
128 break;
129 }
130 lower->type = newEntryType;
131 lower->label = label;
132 parent->emitDataChanged(lowerIndex);
133 break;
134 case CT_DELETED:
135 if(!inModel)
136 {
137 qWarning() << "AddressTablePriv::updateEntry: Warning: Got CT_DELETED, but entry is not in model";
138 break;
139 }
140 parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1);
141 cachedAddressTable.erase(lower, upper);
142 parent->endRemoveRows();
143 break;
144 }
145 }
146
147 int size()
148 {
149 return cachedAddressTable.size();
150 }
151
153 {
154 if(idx >= 0 && idx < cachedAddressTable.size())
155 {
156 return &cachedAddressTable[idx];
157 }
158 else
159 {
160 return nullptr;
161 }
162 }
163};
164
166 QAbstractTableModel(parent), walletModel(parent)
167{
168 columns << tr("Label") << tr("Address");
169 priv = new AddressTablePriv(this);
170 priv->refreshAddressTable(parent->wallet(), pk_hash_only);
171}
172
174{
175 delete priv;
176}
177
178int AddressTableModel::rowCount(const QModelIndex &parent) const
179{
180 if (parent.isValid()) {
181 return 0;
182 }
183 return priv->size();
184}
185
186int AddressTableModel::columnCount(const QModelIndex &parent) const
187{
188 if (parent.isValid()) {
189 return 0;
190 }
191 return columns.length();
192}
193
194QVariant AddressTableModel::data(const QModelIndex &index, int role) const
195{
196 if(!index.isValid())
197 return QVariant();
198
199 AddressTableEntry *rec = static_cast<AddressTableEntry*>(index.internalPointer());
200
201 const auto column = static_cast<ColumnIndex>(index.column());
202 if (role == Qt::DisplayRole || role == Qt::EditRole) {
203 switch (column) {
204 case Label:
205 if (rec->label.isEmpty() && role == Qt::DisplayRole) {
206 return tr("(no label)");
207 } else {
208 return rec->label;
209 }
210 case Address:
211 return rec->address;
212 } // no default case, so the compiler can warn about missing cases
213 assert(false);
214 } else if (role == Qt::FontRole) {
215 switch (column) {
216 case Label:
217 return QFont();
218 case Address:
220 } // no default case, so the compiler can warn about missing cases
221 assert(false);
222 } else if (role == TypeRole) {
223 switch(rec->type)
224 {
226 return Send;
228 return Receive;
230 return {};
231 } // no default case, so the compiler can warn about missing cases
232 assert(false);
233 }
234 return QVariant();
235}
236
237bool AddressTableModel::setData(const QModelIndex &index, const QVariant &value, int role)
238{
239 if(!index.isValid())
240 return false;
241 AddressTableEntry *rec = static_cast<AddressTableEntry*>(index.internalPointer());
242 std::string strPurpose = (rec->type == AddressTableEntry::Sending ? "send" : "receive");
243 editStatus = OK;
244
245 if(role == Qt::EditRole)
246 {
247 CTxDestination curAddress = DecodeDestination(rec->address.toStdString());
248 if(index.column() == Label)
249 {
250 // Do nothing, if old label == new label
251 if(rec->label == value.toString())
252 {
254 return false;
255 }
256 walletModel->wallet().setAddressBook(curAddress, value.toString().toStdString(), strPurpose);
257 } else if(index.column() == Address) {
258 CTxDestination newAddress = DecodeDestination(value.toString().toStdString());
259 // Refuse to set invalid address, set error status and return false
260 if(std::get_if<CNoDestination>(&newAddress))
261 {
263 return false;
264 }
265 // Do nothing, if old address == new address
266 else if(newAddress == curAddress)
267 {
269 return false;
270 }
271 // Check for duplicate addresses to prevent accidental deletion of addresses, if you try
272 // to paste an existing address over another address (with a different label)
274 newAddress, /* name= */ nullptr, /* is_mine= */ nullptr, /* purpose= */ nullptr))
275 {
277 return false;
278 }
279 // Double-check that we're not overwriting a receiving address
280 else if(rec->type == AddressTableEntry::Sending)
281 {
282 // Remove old entry
283 walletModel->wallet().delAddressBook(curAddress);
284 // Add new entry with new address
285 walletModel->wallet().setAddressBook(newAddress, value.toString().toStdString(), strPurpose);
286 }
287 }
288 return true;
289 }
290 return false;
291}
292
293QVariant AddressTableModel::headerData(int section, Qt::Orientation orientation, int role) const
294{
295 if(orientation == Qt::Horizontal)
296 {
297 if(role == Qt::DisplayRole && section < columns.size())
298 {
299 return columns[section];
300 }
301 }
302 return QVariant();
303}
304
305Qt::ItemFlags AddressTableModel::flags(const QModelIndex &index) const
306{
307 if (!index.isValid()) return Qt::NoItemFlags;
308
309 AddressTableEntry *rec = static_cast<AddressTableEntry*>(index.internalPointer());
310
311 Qt::ItemFlags retval = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
312 // Can edit address and label for sending addresses,
313 // and only label for receiving addresses.
314 if(rec->type == AddressTableEntry::Sending ||
315 (rec->type == AddressTableEntry::Receiving && index.column()==Label))
316 {
317 retval |= Qt::ItemIsEditable;
318 }
319 return retval;
320}
321
322QModelIndex AddressTableModel::index(int row, int column, const QModelIndex &parent) const
323{
324 Q_UNUSED(parent);
326 if(data)
327 {
328 return createIndex(row, column, priv->index(row));
329 }
330 else
331 {
332 return QModelIndex();
333 }
334}
335
336void AddressTableModel::updateEntry(const QString &address,
337 const QString &label, bool isMine, const QString &purpose, int status)
338{
339 // Update address book model from Bitcoin core
340 priv->updateEntry(address, label, isMine, purpose, status);
341}
342
343QString AddressTableModel::addRow(const QString &type, const QString &label, const QString &address, const OutputType address_type)
344{
345 std::string strLabel = label.toStdString();
346 std::string strAddress = address.toStdString();
347
348 editStatus = OK;
349
350 if(type == Send)
351 {
352 if(!walletModel->validateAddress(address))
353 {
355 return QString();
356 }
357 // Check for duplicate addresses
358 {
360 DecodeDestination(strAddress), /* name= */ nullptr, /* is_mine= */ nullptr, /* purpose= */ nullptr))
361 {
363 return QString();
364 }
365 }
366
367 // Add entry
368 walletModel->wallet().setAddressBook(DecodeDestination(strAddress), strLabel, "send");
369 }
370 else if(type == Receive)
371 {
372 // Generate a new address to associate with given label
373 CTxDestination dest;
374 if(!walletModel->wallet().getNewDestination(address_type, strLabel, dest))
375 {
377 if(!ctx.isValid())
378 {
379 // Unlock wallet failed or was cancelled
381 return QString();
382 }
383 if(!walletModel->wallet().getNewDestination(address_type, strLabel, dest))
384 {
386 return QString();
387 }
388 }
389 strAddress = EncodeDestination(dest);
390 }
391 else
392 {
393 return QString();
394 }
395 return QString::fromStdString(strAddress);
396}
397
398bool AddressTableModel::removeRows(int row, int count, const QModelIndex &parent)
399{
400 Q_UNUSED(parent);
401 AddressTableEntry *rec = priv->index(row);
402 if(count != 1 || !rec || rec->type == AddressTableEntry::Receiving)
403 {
404 // Can only remove one row at a time, and cannot remove rows not in model.
405 // Also refuse to remove receiving addresses.
406 return false;
407 }
409 return true;
410}
411
412QString AddressTableModel::labelForAddress(const QString &address) const
413{
414 std::string name;
415 if (getAddressData(address, &name, /* purpose= */ nullptr)) {
416 return QString::fromStdString(name);
417 }
418 return QString();
419}
420
421QString AddressTableModel::purposeForAddress(const QString &address) const
422{
423 std::string purpose;
424 if (getAddressData(address, /* name= */ nullptr, &purpose)) {
425 return QString::fromStdString(purpose);
426 }
427 return QString();
428}
429
430bool AddressTableModel::getAddressData(const QString &address,
431 std::string* name,
432 std::string* purpose) const {
433 CTxDestination destination = DecodeDestination(address.toStdString());
434 return walletModel->wallet().getAddress(destination, name, /* is_mine= */ nullptr, purpose);
435}
436
437int AddressTableModel::lookupAddress(const QString &address) const
438{
439 QModelIndexList lst = match(index(0, Address, QModelIndex()),
440 Qt::EditRole, address, 1, Qt::MatchExactly);
441 if(lst.isEmpty())
442 {
443 return -1;
444 }
445 else
446 {
447 return lst.at(0).row();
448 }
449}
450
452
454{
455 Q_EMIT dataChanged(index(idx, 0, QModelIndex()), index(idx, columns.length()-1, QModelIndex()));
456}
static AddressTableEntry::Type translateTransactionType(const QString &strPurpose, bool isMine)
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
AddressTableModel(WalletModel *parent=nullptr, bool pk_hash_only=false)
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.
QString addRow(const QString &type, const QString &label, const QString &address, const OutputType address_type)
void updateEntry(const QString &address, const QString &label, bool isMine, const QString &purpose, int status)
friend class AddressTablePriv
QVariant headerData(int section, Qt::Orientation orientation, int role) const override
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 purposeForAddress(const QString &address) const
Look up purpose for address in address book, if not found return empty string.
QString labelForAddress(const QString &address) const
Look up label for address in address book, if not found return empty string.
bool getAddressData(const QString &address, std::string *name, std::string *purpose) const
Look up address book data given an address string.
QList< AddressTableEntry > cachedAddressTable
AddressTablePriv(AddressTableModel *_parent)
void updateEntry(const QString &address, const QString &label, bool isMine, const QString &purpose, int status)
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:52
bool validateAddress(const QString &address)
interfaces::Wallet & wallet() const
Definition: walletmodel.h:144
UnlockContext requestUnlock()
Interface for accessing a wallet.
Definition: wallet.h:53
virtual bool getNewDestination(const OutputType type, const std::string label, CTxDestination &dest)=0
virtual bool setAddressBook(const CTxDestination &dest, const std::string &name, const std::string &purpose)=0
Add or update address.
virtual OutputType getDefaultAddressType()=0
virtual bool delAddressBook(const CTxDestination &dest)=0
virtual bool getAddress(const CTxDestination &dest, std::string *name, isminetype *is_mine, std::string *purpose)=0
Look up address in wallet, return whether exists.
std::string EncodeDestination(const CTxDestination &dest)
Definition: key_io.cpp:256
CTxDestination DecodeDestination(const std::string &str, std::string &error_msg)
Definition: key_io.cpp:261
QFont fixedPitchFont(bool use_embedded_font)
Definition: guiutil.cpp:88
OutputType
Definition: outputtype.h:18
const char * name
Definition: rest.cpp:43
std::variant< CNoDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessV1Taproot, WitnessUnknown > CTxDestination
A txout script template with a specific destination.
Definition: standard.h:157
Type type
QString label
AddressTableEntry()
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 secp256k1_context * ctx
Definition: tests.c:42
static int count
Definition: tests.c:41
@ CT_UPDATED
@ CT_DELETED
@ CT_NEW
assert(!tx.IsCoinBase())