Bitcoin Core  27.99.0
P2P Digital Currency
addresstablemodel.cpp
Go to the documentation of this file.
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 
23 {
24  enum Type {
27  Hidden /* QSortFilterProxyModel will filter these out */
28  };
29 
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 
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 */
57 {
58  // "refund" addresses aren't shown, and change addresses aren't returned by getAddresses at all.
59  switch (purpose) {
63  } // no default case, so the compiler can warn about missing cases
64  assert(false);
65 }
66 
67 // Private implementation
69 {
70 public:
71  QList<AddressTableEntry> cachedAddressTable;
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  }
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.
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(
104  QList<AddressTableEntry>::iterator upper = std::upper_bound(
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 
152  {
153  if(idx >= 0 && idx < cachedAddressTable.size())
154  {
155  return &cachedAddressTable[idx];
156  }
157  else
158  {
159  return nullptr;
160  }
161  }
162 };
163 
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 
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  {
225  return Send;
227  return Receive;
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());
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  {
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  {
262  return false;
263  }
264  // Do nothing, if old address == new address
265  else if(newAddress == curAddress)
266  {
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)
273  newAddress, /* name= */ nullptr, /* is_mine= */ nullptr, /* purpose= */ nullptr))
274  {
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  {
354  return QString();
355  }
356  // Check for duplicate addresses
357  {
359  DecodeDestination(strAddress), /* name= */ nullptr, /* is_mine= */ nullptr, /* purpose= */ nullptr))
360  {
362  return QString();
363  }
364  }
365 
366  // Add entry
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 {
376  if (!ctx.isValid()) {
377  // Unlock wallet failed or was cancelled
379  return QString();
380  }
381  if (auto dest_retry{walletModel->wallet().getNewDestination(address_type, strLabel)}) {
382  strAddress = EncodeDestination(*dest_retry);
383  } else {
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  }
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, /* 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 }
455 
constexpr AddressTableEntry::Type translateTransactionType(wallet::AddressPurpose purpose, bool isMine)
std::variant< CNoDestination, PubKeyDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessV1Taproot, WitnessUnknown > CTxDestination
A txout script categorized into standard templates.
Definition: addresstype.h:131
Qt model of the address book in the core.
bool removeRows(int row, int count, const QModelIndex &parent=QModelIndex()) override
@ TypeRole
Type of address (Send or Receive)
int lookupAddress(const QString &address) const
AddressTablePriv * priv
OutputType GetDefaultAddressType() const
@ WALLET_UNLOCK_FAILURE
Wallet could not be unlocked to create new receiving address.
@ NO_CHANGES
No changes were made during edit operation.
@ INVALID_ADDRESS
Unparseable address.
@ KEY_GENERATION_FAILURE
Generating a new public key for a receiving address failed.
@ OK
Everything ok.
@ DUPLICATE_ADDRESS
Address already in address book.
void emitDataChanged(int index)
Notify listeners that data changed.
@ Address
Bitcoin address.
@ Label
User specified label.
QVariant data(const QModelIndex &index, int role) const override
QModelIndex index(int row, int column, const QModelIndex &parent) const override
std::optional< wallet::AddressPurpose > purposeForAddress(const QString &address) const
Look up purpose for address in address book, if not found return empty string.
AddressTableModel(WalletModel *parent=nullptr, bool pk_hash_only=false)
bool getAddressData(const QString &address, std::string *name, wallet::AddressPurpose *purpose) const
Look up address book data given an address string.
int columnCount(const QModelIndex &parent) const override
bool setData(const QModelIndex &index, const QVariant &value, int role) override
static const QString Send
Specifies send address.
void updateEntry(const QString &address, const QString &label, bool isMine, wallet::AddressPurpose purpose, int status)
QString addRow(const QString &type, const QString &label, const QString &address, const OutputType address_type)
friend class AddressTablePriv
QVariant headerData(int section, Qt::Orientation orientation, int role) const override
QString GetWalletDisplayName() const
Qt::ItemFlags flags(const QModelIndex &index) const override
static const QString Receive
Specifies receive address.
int rowCount(const QModelIndex &parent) const override
WalletModel *const walletModel
QString labelForAddress(const QString &address) const
Look up label for address in address book, if not found return empty string.
QList< AddressTableEntry > cachedAddressTable
void updateEntry(const QString &address, const QString &label, bool isMine, wallet::AddressPurpose purpose, int status)
AddressTablePriv(AddressTableModel *_parent)
void refreshAddressTable(interfaces::Wallet &wallet, bool pk_hash_only=false)
AddressTableModel * parent
AddressTableEntry * index(int idx)
Interface to Bitcoin wallet from Qt view code.
Definition: walletmodel.h:48
bool validateAddress(const QString &address) const
interfaces::Wallet & wallet() const
Definition: walletmodel.h:138
QString getDisplayName() const
UnlockContext requestUnlock()
Interface for accessing a wallet.
Definition: wallet.h:61
virtual bool getAddress(const CTxDestination &dest, std::string *name, wallet::isminetype *is_mine, wallet::AddressPurpose *purpose)=0
Look up address in wallet, return whether exists.
virtual bool setAddressBook(const CTxDestination &dest, const std::string &name, const std::optional< wallet::AddressPurpose > &purpose)=0
Add or update address.
virtual OutputType getDefaultAddressType()=0
virtual bool delAddressBook(const CTxDestination &dest)=0
CTxDestination DecodeDestination(const std::string &str, std::string &error_msg, std::vector< int > *error_locations)
Definition: key_io.cpp:292
std::string EncodeDestination(const CTxDestination &dest)
Definition: key_io.cpp:287
QFont fixedPitchFont(bool use_embedded_font)
Definition: guiutil.cpp:102
AddressPurpose
Address purpose field that has been been stored with wallet sending and receiving addresses since BIP...
Definition: types.h:60
@ REFUND
Never set in current code may be present in older wallet databases.
CTxDestination getNewDestination(CWallet &w, OutputType output_type)
Returns a new destination, of an specific type, from the wallet.
Definition: util.cpp:91
OutputType
Definition: outputtype.h:17
const char * name
Definition: rest.cpp:48
Type type
QString label
AddressTableEntry()=default
AddressTableEntry(Type _type, const QString &_label, const QString &_address)
QString address
Type
@ Hidden
@ Sending
@ Receiving
bool operator()(const AddressTableEntry &a, const AddressTableEntry &b) const
bool operator()(const QString &a, const AddressTableEntry &b) const
bool operator()(const AddressTableEntry &a, const QString &b) const
static int count
@ CT_UPDATED
@ CT_DELETED
@ CT_NEW
assert(!tx.IsCoinBase())