Bitcoin Core  21.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 
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/wallet.h>
12 
13 #include <algorithm>
14 
15 #include <QFont>
16 #include <QDebug>
17 
18 const QString AddressTableModel::Send = "S";
19 const 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 */
55 static 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 {
71 public:
72  QList<AddressTableEntry> cachedAddressTable;
74 
75  explicit AddressTablePriv(AddressTableModel *_parent):
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.
97  std::sort(cachedAddressTable.begin(), cachedAddressTable.end(), AddressTableEntryLessThan());
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(
104  cachedAddressTable.begin(), cachedAddressTable.end(), address, AddressTableEntryLessThan());
105  QList<AddressTableEntry>::iterator upper = std::upper_bound(
106  cachedAddressTable.begin(), cachedAddressTable.end(), address, AddressTableEntryLessThan());
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 
178 int AddressTableModel::rowCount(const QModelIndex &parent) const
179 {
180  if (parent.isValid()) {
181  return 0;
182  }
183  return priv->size();
184 }
185 
186 int AddressTableModel::columnCount(const QModelIndex &parent) const
187 {
188  if (parent.isValid()) {
189  return 0;
190  }
191  return columns.length();
192 }
193 
194 QVariant 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  if(role == Qt::DisplayRole || role == Qt::EditRole)
202  {
203  switch(index.column())
204  {
205  case Label:
206  if(rec->label.isEmpty() && role == Qt::DisplayRole)
207  {
208  return tr("(no label)");
209  }
210  else
211  {
212  return rec->label;
213  }
214  case Address:
215  return rec->address;
216  }
217  }
218  else if (role == Qt::FontRole)
219  {
220  QFont font;
221  if(index.column() == Address)
222  {
223  font = GUIUtil::fixedPitchFont();
224  }
225  return font;
226  }
227  else if (role == TypeRole)
228  {
229  switch(rec->type)
230  {
232  return Send;
234  return Receive;
235  default: break;
236  }
237  }
238  return QVariant();
239 }
240 
241 bool AddressTableModel::setData(const QModelIndex &index, const QVariant &value, int role)
242 {
243  if(!index.isValid())
244  return false;
245  AddressTableEntry *rec = static_cast<AddressTableEntry*>(index.internalPointer());
246  std::string strPurpose = (rec->type == AddressTableEntry::Sending ? "send" : "receive");
247  editStatus = OK;
248 
249  if(role == Qt::EditRole)
250  {
251  CTxDestination curAddress = DecodeDestination(rec->address.toStdString());
252  if(index.column() == Label)
253  {
254  // Do nothing, if old label == new label
255  if(rec->label == value.toString())
256  {
258  return false;
259  }
260  walletModel->wallet().setAddressBook(curAddress, value.toString().toStdString(), strPurpose);
261  } else if(index.column() == Address) {
262  CTxDestination newAddress = DecodeDestination(value.toString().toStdString());
263  // Refuse to set invalid address, set error status and return false
264  if(std::get_if<CNoDestination>(&newAddress))
265  {
267  return false;
268  }
269  // Do nothing, if old address == new address
270  else if(newAddress == curAddress)
271  {
273  return false;
274  }
275  // Check for duplicate addresses to prevent accidental deletion of addresses, if you try
276  // to paste an existing address over another address (with a different label)
278  newAddress, /* name= */ nullptr, /* is_mine= */ nullptr, /* purpose= */ nullptr))
279  {
281  return false;
282  }
283  // Double-check that we're not overwriting a receiving address
284  else if(rec->type == AddressTableEntry::Sending)
285  {
286  // Remove old entry
287  walletModel->wallet().delAddressBook(curAddress);
288  // Add new entry with new address
289  walletModel->wallet().setAddressBook(newAddress, value.toString().toStdString(), strPurpose);
290  }
291  }
292  return true;
293  }
294  return false;
295 }
296 
297 QVariant AddressTableModel::headerData(int section, Qt::Orientation orientation, int role) const
298 {
299  if(orientation == Qt::Horizontal)
300  {
301  if(role == Qt::DisplayRole && section < columns.size())
302  {
303  return columns[section];
304  }
305  }
306  return QVariant();
307 }
308 
309 Qt::ItemFlags AddressTableModel::flags(const QModelIndex &index) const
310 {
311  if (!index.isValid()) return Qt::NoItemFlags;
312 
313  AddressTableEntry *rec = static_cast<AddressTableEntry*>(index.internalPointer());
314 
315  Qt::ItemFlags retval = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
316  // Can edit address and label for sending addresses,
317  // and only label for receiving addresses.
318  if(rec->type == AddressTableEntry::Sending ||
319  (rec->type == AddressTableEntry::Receiving && index.column()==Label))
320  {
321  retval |= Qt::ItemIsEditable;
322  }
323  return retval;
324 }
325 
326 QModelIndex AddressTableModel::index(int row, int column, const QModelIndex &parent) const
327 {
328  Q_UNUSED(parent);
329  AddressTableEntry *data = priv->index(row);
330  if(data)
331  {
332  return createIndex(row, column, priv->index(row));
333  }
334  else
335  {
336  return QModelIndex();
337  }
338 }
339 
340 void AddressTableModel::updateEntry(const QString &address,
341  const QString &label, bool isMine, const QString &purpose, int status)
342 {
343  // Update address book model from Bitcoin core
344  priv->updateEntry(address, label, isMine, purpose, status);
345 }
346 
347 QString AddressTableModel::addRow(const QString &type, const QString &label, const QString &address, const OutputType address_type)
348 {
349  std::string strLabel = label.toStdString();
350  std::string strAddress = address.toStdString();
351 
352  editStatus = OK;
353 
354  if(type == Send)
355  {
356  if(!walletModel->validateAddress(address))
357  {
359  return QString();
360  }
361  // Check for duplicate addresses
362  {
364  DecodeDestination(strAddress), /* name= */ nullptr, /* is_mine= */ nullptr, /* purpose= */ nullptr))
365  {
367  return QString();
368  }
369  }
370 
371  // Add entry
372  walletModel->wallet().setAddressBook(DecodeDestination(strAddress), strLabel, "send");
373  }
374  else if(type == Receive)
375  {
376  // Generate a new address to associate with given label
377  CTxDestination dest;
378  if(!walletModel->wallet().getNewDestination(address_type, strLabel, dest))
379  {
381  if(!ctx.isValid())
382  {
383  // Unlock wallet failed or was cancelled
385  return QString();
386  }
387  if(!walletModel->wallet().getNewDestination(address_type, strLabel, dest))
388  {
390  return QString();
391  }
392  }
393  strAddress = EncodeDestination(dest);
394  }
395  else
396  {
397  return QString();
398  }
399  return QString::fromStdString(strAddress);
400 }
401 
402 bool AddressTableModel::removeRows(int row, int count, const QModelIndex &parent)
403 {
404  Q_UNUSED(parent);
405  AddressTableEntry *rec = priv->index(row);
406  if(count != 1 || !rec || rec->type == AddressTableEntry::Receiving)
407  {
408  // Can only remove one row at a time, and cannot remove rows not in model.
409  // Also refuse to remove receiving addresses.
410  return false;
411  }
413  return true;
414 }
415 
416 QString AddressTableModel::labelForAddress(const QString &address) const
417 {
418  std::string name;
419  if (getAddressData(address, &name, /* purpose= */ nullptr)) {
420  return QString::fromStdString(name);
421  }
422  return QString();
423 }
424 
425 QString AddressTableModel::purposeForAddress(const QString &address) const
426 {
427  std::string purpose;
428  if (getAddressData(address, /* name= */ nullptr, &purpose)) {
429  return QString::fromStdString(purpose);
430  }
431  return QString();
432 }
433 
434 bool AddressTableModel::getAddressData(const QString &address,
435  std::string* name,
436  std::string* purpose) const {
437  CTxDestination destination = DecodeDestination(address.toStdString());
438  return walletModel->wallet().getAddress(destination, name, /* is_mine= */ nullptr, purpose);
439 }
440 
441 int AddressTableModel::lookupAddress(const QString &address) const
442 {
443  QModelIndexList lst = match(index(0, Address, QModelIndex()),
444  Qt::EditRole, address, 1, Qt::MatchExactly);
445  if(lst.isEmpty())
446  {
447  return -1;
448  }
449  else
450  {
451  return lst.at(0).row();
452  }
453 }
454 
456 
458 {
459  Q_EMIT dataChanged(index(idx, 0, QModelIndex()), index(idx, columns.length()-1, QModelIndex()));
460 }
QVariant data(const QModelIndex &index, int role) const override
virtual bool getAddress(const CTxDestination &dest, std::string *name, isminetype *is_mine, std::string *purpose)=0
Look up address in wallet, return whether exists.
Generating a new public key for a receiving address failed.
QString address
interfaces::Wallet & wallet() const
Definition: walletmodel.h:146
bool operator()(const QString &a, const AddressTableEntry &b) const
QFont fixedPitchFont()
Definition: guiutil.cpp:79
bool getAddressData(const QString &address, std::string *name, std::string *purpose) const
Look up address book data given an address string.
int lookupAddress(const QString &address) const
virtual OutputType getDefaultAddressType()=0
QVariant headerData(int section, Qt::Orientation orientation, int role) const override
UnlockContext requestUnlock()
AddressTableEntry * index(int idx)
Address already in address book.
int rowCount(const QModelIndex &parent) const override
Type
Type type
std::variant< CNoDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessUnknown > CTxDestination
A txout script template with a specific destination.
Definition: standard.h:212
QString purposeForAddress(const QString &address) const
Look up purpose for address in address book, if not found return empty string.
OutputType
Definition: outputtype.h:17
QString addRow(const QString &type, const QString &label, const QString &address, const OutputType address_type)
static const QString Send
Specifies send address.
bool operator()(const AddressTableEntry &a, const AddressTableEntry &b) const
int columnCount(const QModelIndex &parent) const override
bool setData(const QModelIndex &index, const QVariant &value, int role) override
Wallet could not be unlocked to create new receiving address.
QList< AddressTableEntry > cachedAddressTable
Qt::ItemFlags flags(const QModelIndex &index) const override
const char * name
Definition: rest.cpp:41
static secp256k1_context * ctx
Definition: tests.c:36
QString labelForAddress(const QString &address) const
Look up label for address in address book, if not found return empty string.
Interface for accessing a wallet.
Definition: wallet.h:52
bool removeRows(int row, int count, const QModelIndex &parent=QModelIndex()) override
AddressTableModel * parent
WalletModel *const walletModel
Qt model of the address book in the core.
AddressTablePriv(AddressTableModel *_parent)
AddressTableEntry()
AddressTableEntry(Type _type, const QString &_label, const QString &_address)
OutputType GetDefaultAddressType() const
bool validateAddress(const QString &address)
CTxDestination DecodeDestination(const std::string &str)
Definition: key_io.cpp:211
virtual bool getNewDestination(const OutputType type, const std::string label, CTxDestination &dest)=0
bool operator()(const AddressTableEntry &a, const QString &b) const
virtual bool delAddressBook(const CTxDestination &dest)=0
AddressTablePriv * priv
Interface to Bitcoin wallet from Qt view code.
Definition: walletmodel.h:51
void updateEntry(const QString &address, const QString &label, bool isMine, const QString &purpose, int status)
static const QString Receive
Specifies receive address.
void emitDataChanged(int index)
Notify listeners that data changed.
virtual bool setAddressBook(const CTxDestination &dest, const std::string &name, const std::string &purpose)=0
Add or update address.
static int count
Definition: tests.c:35
void refreshAddressTable(interfaces::Wallet &wallet, bool pk_hash_only=false)
std::string EncodeDestination(const CTxDestination &dest)
Definition: key_io.cpp:206
QString label
No changes were made during edit operation.
User specified label.
static AddressTableEntry::Type translateTransactionType(const QString &strPurpose, bool isMine)
friend class AddressTablePriv
QModelIndex index(int row, int column, const QModelIndex &parent) const override
void updateEntry(const QString &address, const QString &label, bool isMine, const QString &purpose, int status)
AddressTableModel(WalletModel *parent=nullptr, bool pk_hash_only=false)
Type of address (Send or Receive)
virtual std::vector< WalletAddress > getAddresses()=0
Get wallet address list.