Bitcoin Core  0.20.99
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 #include <typeinfo>
15 
16 #include <QFont>
17 #include <QDebug>
18 
19 const QString AddressTableModel::Send = "S";
20 const QString AddressTableModel::Receive = "R";
21 
23 {
24  enum Type {
27  Hidden /* QSortFilterProxyModel will filter these out */
28  };
29 
31  QString label;
32  QString address;
33 
35  AddressTableEntry(Type _type, const QString &_label, const QString &_address):
36  type(_type), label(_label), address(_address) {}
37 };
38 
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 static AddressTableEntry::Type translateTransactionType(const QString &strPurpose, bool isMine)
57 {
59  // "refund" addresses aren't shown, and change addresses aren't returned by getAddresses at all.
60  if (strPurpose == "send")
61  addressType = AddressTableEntry::Sending;
62  else if (strPurpose == "receive")
63  addressType = AddressTableEntry::Receiving;
64  else if (strPurpose == "unknown" || strPurpose == "") // if purpose not set, guess
66  return addressType;
67 }
68 
69 // Private implementation
71 {
72 public:
73  QList<AddressTableEntry> cachedAddressTable;
75 
76  explicit AddressTablePriv(AddressTableModel *_parent):
77  parent(_parent) {}
78 
79  void refreshAddressTable(interfaces::Wallet& wallet, bool pk_hash_only = false)
80  {
81  cachedAddressTable.clear();
82  {
83  for (const auto& address : wallet.getAddresses())
84  {
85  if (pk_hash_only && address.dest.type() != typeid(PKHash))
86  continue;
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  Q_UNUSED(parent);
181  return priv->size();
182 }
183 
184 int AddressTableModel::columnCount(const QModelIndex &parent) const
185 {
186  Q_UNUSED(parent);
187  return columns.length();
188 }
189 
190 QVariant AddressTableModel::data(const QModelIndex &index, int role) const
191 {
192  if(!index.isValid())
193  return QVariant();
194 
195  AddressTableEntry *rec = static_cast<AddressTableEntry*>(index.internalPointer());
196 
197  if(role == Qt::DisplayRole || role == Qt::EditRole)
198  {
199  switch(index.column())
200  {
201  case Label:
202  if(rec->label.isEmpty() && role == Qt::DisplayRole)
203  {
204  return tr("(no label)");
205  }
206  else
207  {
208  return rec->label;
209  }
210  case Address:
211  return rec->address;
212  }
213  }
214  else if (role == Qt::FontRole)
215  {
216  QFont font;
217  if(index.column() == Address)
218  {
219  font = GUIUtil::fixedPitchFont();
220  }
221  return font;
222  }
223  else if (role == TypeRole)
224  {
225  switch(rec->type)
226  {
228  return Send;
230  return Receive;
231  default: break;
232  }
233  }
234  return QVariant();
235 }
236 
237 bool 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(boost::get<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 
293 QVariant 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 
305 Qt::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 
322 QModelIndex AddressTableModel::index(int row, int column, const QModelIndex &parent) const
323 {
324  Q_UNUSED(parent);
325  AddressTableEntry *data = priv->index(row);
326  if(data)
327  {
328  return createIndex(row, column, priv->index(row));
329  }
330  else
331  {
332  return QModelIndex();
333  }
334 }
335 
336 void 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 
343 QString 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 
398 bool 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 
412 QString 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 
421 QString 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 
430 bool 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 
437 int 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 }
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:83
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
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:50
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:215
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:210
QString label
No changes were made during edit operation.
boost::variant< CNoDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessUnknown > CTxDestination
A txout script template with a specific destination.
Definition: standard.h:209
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.