/ src / qt / addresstablemodel.cpp
addresstablemodel.cpp
  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  
  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, /* is_mine= */ 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, /* is_mine= */ 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          auto op_dest = walletModel->wallet().getNewDestination(address_type, strLabel);
373          if (!op_dest) {
374              WalletModel::UnlockContext ctx(walletModel->requestUnlock());
375              if (!ctx.isValid()) {
376                  // Unlock wallet failed or was cancelled
377                  editStatus = WALLET_UNLOCK_FAILURE;
378                  return QString();
379              }
380              op_dest = walletModel->wallet().getNewDestination(address_type, strLabel);
381              if (!op_dest) {
382                  editStatus = KEY_GENERATION_FAILURE;
383                  return QString();
384              }
385          }
386          strAddress = EncodeDestination(*op_dest);
387      }
388      else
389      {
390          return QString();
391      }
392      return QString::fromStdString(strAddress);
393  }
394  
395  bool AddressTableModel::removeRows(int row, int count, const QModelIndex &parent)
396  {
397      Q_UNUSED(parent);
398      AddressTableEntry *rec = priv->index(row);
399      if(count != 1 || !rec || rec->type == AddressTableEntry::Receiving)
400      {
401          // Can only remove one row at a time, and cannot remove rows not in model.
402          // Also refuse to remove receiving addresses.
403          return false;
404      }
405      walletModel->wallet().delAddressBook(DecodeDestination(rec->address.toStdString()));
406      return true;
407  }
408  
409  QString AddressTableModel::labelForAddress(const QString &address) const
410  {
411      std::string name;
412      if (getAddressData(address, &name, /* purpose= */ nullptr)) {
413          return QString::fromStdString(name);
414      }
415      return QString();
416  }
417  
418  std::optional<wallet::AddressPurpose> AddressTableModel::purposeForAddress(const QString &address) const
419  {
420      wallet::AddressPurpose purpose;
421      if (getAddressData(address, /* name= */ nullptr, &purpose)) {
422          return purpose;
423      }
424      return std::nullopt;
425  }
426  
427  bool AddressTableModel::getAddressData(const QString &address,
428          std::string* name,
429          wallet::AddressPurpose* purpose) const {
430      CTxDestination destination = DecodeDestination(address.toStdString());
431      return walletModel->wallet().getAddress(destination, name, /* is_mine= */ nullptr, purpose);
432  }
433  
434  int AddressTableModel::lookupAddress(const QString &address) const
435  {
436      QModelIndexList lst = match(index(0, Address, QModelIndex()),
437                                  Qt::EditRole, address, 1, Qt::MatchExactly);
438      if(lst.isEmpty())
439      {
440          return -1;
441      }
442      else
443      {
444          return lst.at(0).row();
445      }
446  }
447  
448  OutputType AddressTableModel::GetDefaultAddressType() const { return walletModel->wallet().getDefaultAddressType(); };
449  
450  void AddressTableModel::emitDataChanged(int idx)
451  {
452      Q_EMIT dataChanged(index(idx, 0, QModelIndex()), index(idx, columns.length()-1, QModelIndex()));
453  }
454  
455  QString AddressTableModel::GetWalletDisplayName() const { return walletModel->getDisplayName(); };