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