addresstablemodel.cpp raw
1 // Copyright (c) 2011-present 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
5 #include <qt/addresstablemodel.h>
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
19 const QString AddressTableModel::Send = "S";
20 const QString AddressTableModel::Receive = "R";
21
22 struct AddressTableEntry
23 {
24 enum Type {
25 Sending,
26 Receiving,
27 Hidden /* QSortFilterProxyModel will filter these out */
28 };
29
30 Type type;
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
39 struct AddressTableEntryLessThan
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 */
56 constexpr AddressTableEntry::Type translateTransactionType(wallet::AddressPurpose purpose, bool isMine)
57 {
58 // "refund" addresses aren't shown, and change addresses aren't returned by getAddresses at all.
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, so the compiler can warn about missing cases
64 assert(false);
65 }
66
67 // Private implementation
68 class AddressTablePriv
69 {
70 public:
71 QList<AddressTableEntry> cachedAddressTable;
72 AddressTableModel *parent;
73
74 explicit AddressTablePriv(AddressTableModel *_parent):
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 }
86 AddressTableEntry::Type addressType = translateTransactionType(
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.
96 std::sort(cachedAddressTable.begin(), cachedAddressTable.end(), AddressTableEntryLessThan());
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(
103 cachedAddressTable.begin(), cachedAddressTable.end(), address, AddressTableEntryLessThan());
104 QList<AddressTableEntry>::iterator upper = std::upper_bound(
105 cachedAddressTable.begin(), cachedAddressTable.end(), address, AddressTableEntryLessThan());
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
151 AddressTableEntry *index(int idx)
152 {
153 if(idx >= 0 && idx < cachedAddressTable.size())
154 {
155 return &cachedAddressTable[idx];
156 }
157 else
158 {
159 return nullptr;
160 }
161 }
162 };
163
164 AddressTableModel::AddressTableModel(WalletModel *parent, bool pk_hash_only) :
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
172 AddressTableModel::~AddressTableModel()
173 {
174 delete priv;
175 }
176
177 int AddressTableModel::rowCount(const QModelIndex &parent) const
178 {
179 if (parent.isValid()) {
180 return 0;
181 }
182 return priv->size();
183 }
184
185 int AddressTableModel::columnCount(const QModelIndex &parent) const
186 {
187 if (parent.isValid()) {
188 return 0;
189 }
190 return columns.length();
191 }
192
193 QVariant 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:
218 return GUIUtil::fixedPitchFont();
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 {
224 case AddressTableEntry::Sending:
225 return Send;
226 case AddressTableEntry::Receiving:
227 return Receive;
228 case AddressTableEntry::Hidden:
229 return {};
230 } // no default case, so the compiler can warn about missing cases
231 assert(false);
232 }
233 return QVariant();
234 }
235
236 bool 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());
241 wallet::AddressPurpose purpose = rec->type == AddressTableEntry::Sending ? wallet::AddressPurpose::SEND : wallet::AddressPurpose::RECEIVE;
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 {
252 editStatus = NO_CHANGES;
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 {
261 editStatus = INVALID_ADDRESS;
262 return false;
263 }
264 // Do nothing, if old address == new address
265 else if(newAddress == curAddress)
266 {
267 editStatus = NO_CHANGES;
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)
272 if (walletModel->wallet().getAddress(
273 newAddress, /*name=*/nullptr, /*purpose=*/ nullptr))
274 {
275 editStatus = DUPLICATE_ADDRESS;
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
292 QVariant 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
304 Qt::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
321 QModelIndex AddressTableModel::index(int row, int column, const QModelIndex &parent) const
322 {
323 Q_UNUSED(parent);
324 AddressTableEntry *data = priv->index(row);
325 if(data)
326 {
327 return createIndex(row, column, priv->index(row));
328 }
329 else
330 {
331 return QModelIndex();
332 }
333 }
334
335 void 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
342 QString 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 {
353 editStatus = INVALID_ADDRESS;
354 return QString();
355 }
356 // Check for duplicate addresses
357 {
358 if (walletModel->wallet().getAddress(
359 DecodeDestination(strAddress), /*name=*/nullptr, /*purpose=*/nullptr))
360 {
361 editStatus = DUPLICATE_ADDRESS;
362 return QString();
363 }
364 }
365
366 // Add entry
367 walletModel->wallet().setAddressBook(DecodeDestination(strAddress), strLabel, wallet::AddressPurpose::SEND);
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 {
375 WalletModel::UnlockContext ctx(walletModel->requestUnlock());
376 if (!ctx.isValid()) {
377 // Unlock wallet failed or was cancelled
378 editStatus = WALLET_UNLOCK_FAILURE;
379 return QString();
380 }
381 if (auto dest_retry{walletModel->wallet().getNewDestination(address_type, strLabel)}) {
382 strAddress = EncodeDestination(*dest_retry);
383 } else {
384 editStatus = KEY_GENERATION_FAILURE;
385 return QString();
386 }
387 }
388 }
389 else
390 {
391 return QString();
392 }
393 return QString::fromStdString(strAddress);
394 }
395
396 bool 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 }
406 walletModel->wallet().delAddressBook(DecodeDestination(rec->address.toStdString()));
407 return true;
408 }
409
410 QString 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
419 std::optional<wallet::AddressPurpose> AddressTableModel::purposeForAddress(const QString &address) const
420 {
421 wallet::AddressPurpose purpose;
422 if (getAddressData(address, /* name= */ nullptr, &purpose)) {
423 return purpose;
424 }
425 return std::nullopt;
426 }
427
428 bool 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, purpose);
433 }
434
435 int 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
449 OutputType AddressTableModel::GetDefaultAddressType() const { return walletModel->wallet().getDefaultAddressType(); };
450
451 void AddressTableModel::emitDataChanged(int idx)
452 {
453 Q_EMIT dataChanged(index(idx, 0, QModelIndex()), index(idx, columns.length()-1, QModelIndex()));
454 }
455
456 QString AddressTableModel::GetWalletDisplayName() const { return walletModel->getDisplayName(); };
457