Bitcoin Core  0.18.99
P2P Digital Currency
addresstablemodel.cpp
Go to the documentation of this file.
1 // Copyright (c) 2011-2018 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 <interfaces/node.h>
11 #include <key_io.h>
12 #include <wallet/wallet.h>
13 
14 #include <QFont>
15 #include <QDebug>
16 
17 const QString AddressTableModel::Send = "S";
18 const QString AddressTableModel::Receive = "R";
19 
21 {
22  enum Type {
25  Hidden /* QSortFilterProxyModel will filter these out */
26  };
27 
29  QString label;
30  QString address;
31 
33  AddressTableEntry(Type _type, const QString &_label, const QString &_address):
34  type(_type), label(_label), address(_address) {}
35 };
36 
38 {
39  bool operator()(const AddressTableEntry &a, const AddressTableEntry &b) const
40  {
41  return a.address < b.address;
42  }
43  bool operator()(const AddressTableEntry &a, const QString &b) const
44  {
45  return a.address < b;
46  }
47  bool operator()(const QString &a, const AddressTableEntry &b) const
48  {
49  return a < b.address;
50  }
51 };
52 
53 /* Determine address type from address purpose */
54 static AddressTableEntry::Type translateTransactionType(const QString &strPurpose, bool isMine)
55 {
57  // "refund" addresses aren't shown, and change addresses aren't in mapAddressBook at all.
58  if (strPurpose == "send")
59  addressType = AddressTableEntry::Sending;
60  else if (strPurpose == "receive")
61  addressType = AddressTableEntry::Receiving;
62  else if (strPurpose == "unknown" || strPurpose == "") // if purpose not set, guess
64  return addressType;
65 }
66 
67 // Private implementation
69 {
70 public:
71  QList<AddressTableEntry> cachedAddressTable;
73 
74  explicit AddressTablePriv(AddressTableModel *_parent):
75  parent(_parent) {}
76 
78  {
79  cachedAddressTable.clear();
80  {
81  for (const auto& address : wallet.getAddresses())
82  {
83  AddressTableEntry::Type addressType = translateTransactionType(
84  QString::fromStdString(address.purpose), address.is_mine);
85  cachedAddressTable.append(AddressTableEntry(addressType,
86  QString::fromStdString(address.name),
87  QString::fromStdString(EncodeDestination(address.dest))));
88  }
89  }
90  // qLowerBound() and qUpperBound() require our cachedAddressTable list to be sorted in asc order
91  // Even though the map is already sorted this re-sorting step is needed because the originating map
92  // is sorted by binary address, not by base58() address.
93  qSort(cachedAddressTable.begin(), cachedAddressTable.end(), AddressTableEntryLessThan());
94  }
95 
96  void updateEntry(const QString &address, const QString &label, bool isMine, const QString &purpose, int status)
97  {
98  // Find address / label in model
99  QList<AddressTableEntry>::iterator lower = qLowerBound(
100  cachedAddressTable.begin(), cachedAddressTable.end(), address, AddressTableEntryLessThan());
101  QList<AddressTableEntry>::iterator upper = qUpperBound(
102  cachedAddressTable.begin(), cachedAddressTable.end(), address, AddressTableEntryLessThan());
103  int lowerIndex = (lower - cachedAddressTable.begin());
104  int upperIndex = (upper - cachedAddressTable.begin());
105  bool inModel = (lower != upper);
106  AddressTableEntry::Type newEntryType = translateTransactionType(purpose, isMine);
107 
108  switch(status)
109  {
110  case CT_NEW:
111  if(inModel)
112  {
113  qWarning() << "AddressTablePriv::updateEntry: Warning: Got CT_NEW, but entry is already in model";
114  break;
115  }
116  parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex);
117  cachedAddressTable.insert(lowerIndex, AddressTableEntry(newEntryType, label, address));
118  parent->endInsertRows();
119  break;
120  case CT_UPDATED:
121  if(!inModel)
122  {
123  qWarning() << "AddressTablePriv::updateEntry: Warning: Got CT_UPDATED, but entry is not in model";
124  break;
125  }
126  lower->type = newEntryType;
127  lower->label = label;
128  parent->emitDataChanged(lowerIndex);
129  break;
130  case CT_DELETED:
131  if(!inModel)
132  {
133  qWarning() << "AddressTablePriv::updateEntry: Warning: Got CT_DELETED, but entry is not in model";
134  break;
135  }
136  parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1);
137  cachedAddressTable.erase(lower, upper);
138  parent->endRemoveRows();
139  break;
140  }
141  }
142 
143  int size()
144  {
145  return cachedAddressTable.size();
146  }
147 
149  {
150  if(idx >= 0 && idx < cachedAddressTable.size())
151  {
152  return &cachedAddressTable[idx];
153  }
154  else
155  {
156  return nullptr;
157  }
158  }
159 };
160 
162  QAbstractTableModel(parent), walletModel(parent)
163 {
164  columns << tr("Label") << tr("Address");
165  priv = new AddressTablePriv(this);
166  priv->refreshAddressTable(parent->wallet());
167 }
168 
170 {
171  delete priv;
172 }
173 
174 int AddressTableModel::rowCount(const QModelIndex &parent) const
175 {
176  Q_UNUSED(parent);
177  return priv->size();
178 }
179 
180 int AddressTableModel::columnCount(const QModelIndex &parent) const
181 {
182  Q_UNUSED(parent);
183  return columns.length();
184 }
185 
186 QVariant AddressTableModel::data(const QModelIndex &index, int role) const
187 {
188  if(!index.isValid())
189  return QVariant();
190 
191  AddressTableEntry *rec = static_cast<AddressTableEntry*>(index.internalPointer());
192 
193  if(role == Qt::DisplayRole || role == Qt::EditRole)
194  {
195  switch(index.column())
196  {
197  case Label:
198  if(rec->label.isEmpty() && role == Qt::DisplayRole)
199  {
200  return tr("(no label)");
201  }
202  else
203  {
204  return rec->label;
205  }
206  case Address:
207  return rec->address;
208  }
209  }
210  else if (role == Qt::FontRole)
211  {
212  QFont font;
213  if(index.column() == Address)
214  {
215  font = GUIUtil::fixedPitchFont();
216  }
217  return font;
218  }
219  else if (role == TypeRole)
220  {
221  switch(rec->type)
222  {
224  return Send;
226  return Receive;
227  default: break;
228  }
229  }
230  return QVariant();
231 }
232 
233 bool AddressTableModel::setData(const QModelIndex &index, const QVariant &value, int role)
234 {
235  if(!index.isValid())
236  return false;
237  AddressTableEntry *rec = static_cast<AddressTableEntry*>(index.internalPointer());
238  std::string strPurpose = (rec->type == AddressTableEntry::Sending ? "send" : "receive");
239  editStatus = OK;
240 
241  if(role == Qt::EditRole)
242  {
243  CTxDestination curAddress = DecodeDestination(rec->address.toStdString());
244  if(index.column() == Label)
245  {
246  // Do nothing, if old label == new label
247  if(rec->label == value.toString())
248  {
250  return false;
251  }
252  walletModel->wallet().setAddressBook(curAddress, value.toString().toStdString(), strPurpose);
253  } else if(index.column() == Address) {
254  CTxDestination newAddress = DecodeDestination(value.toString().toStdString());
255  // Refuse to set invalid address, set error status and return false
256  if(boost::get<CNoDestination>(&newAddress))
257  {
259  return false;
260  }
261  // Do nothing, if old address == new address
262  else if(newAddress == curAddress)
263  {
265  return false;
266  }
267  // Check for duplicate addresses to prevent accidental deletion of addresses, if you try
268  // to paste an existing address over another address (with a different label)
270  newAddress, /* name= */ nullptr, /* is_mine= */ nullptr, /* purpose= */ nullptr))
271  {
273  return false;
274  }
275  // Double-check that we're not overwriting a receiving address
276  else if(rec->type == AddressTableEntry::Sending)
277  {
278  // Remove old entry
279  walletModel->wallet().delAddressBook(curAddress);
280  // Add new entry with new address
281  walletModel->wallet().setAddressBook(newAddress, value.toString().toStdString(), strPurpose);
282  }
283  }
284  return true;
285  }
286  return false;
287 }
288 
289 QVariant AddressTableModel::headerData(int section, Qt::Orientation orientation, int role) const
290 {
291  if(orientation == Qt::Horizontal)
292  {
293  if(role == Qt::DisplayRole && section < columns.size())
294  {
295  return columns[section];
296  }
297  }
298  return QVariant();
299 }
300 
301 Qt::ItemFlags AddressTableModel::flags(const QModelIndex &index) const
302 {
303  if (!index.isValid()) return Qt::NoItemFlags;
304 
305  AddressTableEntry *rec = static_cast<AddressTableEntry*>(index.internalPointer());
306 
307  Qt::ItemFlags retval = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
308  // Can edit address and label for sending addresses,
309  // and only label for receiving addresses.
310  if(rec->type == AddressTableEntry::Sending ||
311  (rec->type == AddressTableEntry::Receiving && index.column()==Label))
312  {
313  retval |= Qt::ItemIsEditable;
314  }
315  return retval;
316 }
317 
318 QModelIndex AddressTableModel::index(int row, int column, const QModelIndex &parent) const
319 {
320  Q_UNUSED(parent);
321  AddressTableEntry *data = priv->index(row);
322  if(data)
323  {
324  return createIndex(row, column, priv->index(row));
325  }
326  else
327  {
328  return QModelIndex();
329  }
330 }
331 
332 void AddressTableModel::updateEntry(const QString &address,
333  const QString &label, bool isMine, const QString &purpose, int status)
334 {
335  // Update address book model from Bitcoin core
336  priv->updateEntry(address, label, isMine, purpose, status);
337 }
338 
339 QString AddressTableModel::addRow(const QString &type, const QString &label, const QString &address, const OutputType address_type)
340 {
341  std::string strLabel = label.toStdString();
342  std::string strAddress = address.toStdString();
343 
344  editStatus = OK;
345 
346  if(type == Send)
347  {
348  if(!walletModel->validateAddress(address))
349  {
351  return QString();
352  }
353  // Check for duplicate addresses
354  {
356  DecodeDestination(strAddress), /* name= */ nullptr, /* is_mine= */ nullptr, /* purpose= */ nullptr))
357  {
359  return QString();
360  }
361  }
362  }
363  else if(type == Receive)
364  {
365  // Generate a new address to associate with given label
366  CPubKey newKey;
367  if(!walletModel->wallet().getKeyFromPool(false /* internal */, newKey))
368  {
370  if(!ctx.isValid())
371  {
372  // Unlock wallet failed or was cancelled
374  return QString();
375  }
376  if(!walletModel->wallet().getKeyFromPool(false /* internal */, newKey))
377  {
379  return QString();
380  }
381  }
382  walletModel->wallet().learnRelatedScripts(newKey, address_type);
383  strAddress = EncodeDestination(GetDestinationForKey(newKey, address_type));
384  }
385  else
386  {
387  return QString();
388  }
389 
390  // Add entry
391  walletModel->wallet().setAddressBook(DecodeDestination(strAddress), strLabel,
392  (type == Send ? "send" : "receive"));
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  }
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 QString AddressTableModel::purposeForAddress(const QString &address) const
420 {
421  std::string purpose;
422  if (getAddressData(address, /* name= */ nullptr, &purpose)) {
423  return QString::fromStdString(purpose);
424  }
425  return QString();
426 }
427 
428 bool AddressTableModel::getAddressData(const QString &address,
429  std::string* name,
430  std::string* purpose) const {
431  CTxDestination destination = DecodeDestination(address.toStdString());
432  return walletModel->wallet().getAddress(destination, name, /* is_mine= */ nullptr, 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 
450 
452 {
453  Q_EMIT dataChanged(index(idx, 0, QModelIndex()), index(idx, columns.length()-1, QModelIndex()));
454 }
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
void refreshAddressTable(interfaces::Wallet &wallet)
interfaces::Wallet & wallet() const
Definition: walletmodel.h:223
bool operator()(const QString &a, const AddressTableEntry &b) const
QFont fixedPitchFont()
Definition: guiutil.cpp:77
bool getAddressData(const QString &address, std::string *name, std::string *purpose) const
Look up address book data given an address string.
QModelIndex index(int row, int column, const QModelIndex &parent) const
int lookupAddress(const QString &address) const
Qt::ItemFlags flags(const QModelIndex &index) const
virtual OutputType getDefaultAddressType()=0
UnlockContext requestUnlock()
AddressTableEntry * index(int idx)
Address already in address book.
Type
Type type
QVariant headerData(int section, Qt::Orientation orientation, int role) const
QString purposeForAddress(const QString &address) const
Look up purpose for address in address book, if not found return empty string.
OutputType
Definition: outputtype.h:16
QString addRow(const QString &type, const QString &label, const QString &address, const OutputType address_type)
static const QString Send
Specifies send address.
int rowCount(const QModelIndex &parent) const
int columnCount(const QModelIndex &parent) const
bool operator()(const AddressTableEntry &a, const AddressTableEntry &b) const
virtual bool getKeyFromPool(bool internal, CPubKey &pub_key)=0
Wallet could not be unlocked to create new receiving address.
QList< AddressTableEntry > cachedAddressTable
const char * name
Definition: rest.cpp:38
AddressTableModel(WalletModel *parent=nullptr)
An encapsulated public key.
Definition: pubkey.h:30
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:46
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:216
virtual void learnRelatedScripts(const CPubKey &key, OutputType type)=0
Add scripts to key store so old so software versions opening the wallet database can detect payments ...
bool removeRows(int row, int count, const QModelIndex &parent=QModelIndex())
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:125
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.
QVariant data(const QModelIndex &index, int role) const
virtual bool setAddressBook(const CTxDestination &dest, const std::string &name, const std::string &purpose)=0
Add or update address.
CTxDestination GetDestinationForKey(const CPubKey &key, OutputType type)
Get a destination of the requested type (if possible) to the specified key.
Definition: outputtype.cpp:45
std::string EncodeDestination(const CTxDestination &dest)
Definition: key_io.cpp:211
bool setData(const QModelIndex &index, const QVariant &value, int role)
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:139
User specified label.
friend class AddressTablePriv
void updateEntry(const QString &address, const QString &label, bool isMine, const QString &purpose, int status)
Type of address (Send or Receive)
virtual std::vector< WalletAddress > getAddresses()=0
Get wallet address list.