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 
20 struct AddressTableEntry
21 {
22     enum Type {
23         Sending,
24         Receiving,
25         Hidden /* QSortFilterProxyModel will filter these out */
26     };
27 
28     Type type;
29     QString label;
30     QString address;
31 
AddressTableEntryAddressTableEntry32     AddressTableEntry() {}
AddressTableEntryAddressTableEntry33     AddressTableEntry(Type _type, const QString &_label, const QString &_address):
34         type(_type), label(_label), address(_address) {}
35 };
36 
37 struct AddressTableEntryLessThan
38 {
operator ()AddressTableEntryLessThan39     bool operator()(const AddressTableEntry &a, const AddressTableEntry &b) const
40     {
41         return a.address < b.address;
42     }
operator ()AddressTableEntryLessThan43     bool operator()(const AddressTableEntry &a, const QString &b) const
44     {
45         return a.address < b;
46     }
operator ()AddressTableEntryLessThan47     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 */
translateTransactionType(const QString & strPurpose,bool isMine)54 static AddressTableEntry::Type translateTransactionType(const QString &strPurpose, bool isMine)
55 {
56     AddressTableEntry::Type addressType = AddressTableEntry::Hidden;
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
63         addressType = (isMine ? AddressTableEntry::Receiving : AddressTableEntry::Sending);
64     return addressType;
65 }
66 
67 // Private implementation
68 class AddressTablePriv
69 {
70 public:
71     QList<AddressTableEntry> cachedAddressTable;
72     AddressTableModel *parent;
73 
AddressTablePriv(AddressTableModel * _parent)74     explicit AddressTablePriv(AddressTableModel *_parent):
75         parent(_parent) {}
76 
refreshAddressTable(interfaces::Wallet & wallet)77     void refreshAddressTable(interfaces::Wallet& wallet)
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 
updateEntry(const QString & address,const QString & label,bool isMine,const QString & purpose,int status)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 
size()143     int size()
144     {
145         return cachedAddressTable.size();
146     }
147 
index(int idx)148     AddressTableEntry *index(int idx)
149     {
150         if(idx >= 0 && idx < cachedAddressTable.size())
151         {
152             return &cachedAddressTable[idx];
153         }
154         else
155         {
156             return nullptr;
157         }
158     }
159 };
160 
AddressTableModel(WalletModel * parent)161 AddressTableModel::AddressTableModel(WalletModel *parent) :
162     QAbstractTableModel(parent), walletModel(parent)
163 {
164     columns << tr("Label") << tr("Address");
165     priv = new AddressTablePriv(this);
166     priv->refreshAddressTable(parent->wallet());
167 }
168 
~AddressTableModel()169 AddressTableModel::~AddressTableModel()
170 {
171     delete priv;
172 }
173 
rowCount(const QModelIndex & parent) const174 int AddressTableModel::rowCount(const QModelIndex &parent) const
175 {
176     Q_UNUSED(parent);
177     return priv->size();
178 }
179 
columnCount(const QModelIndex & parent) const180 int AddressTableModel::columnCount(const QModelIndex &parent) const
181 {
182     Q_UNUSED(parent);
183     return columns.length();
184 }
185 
data(const QModelIndex & index,int role) const186 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         {
223         case AddressTableEntry::Sending:
224             return Send;
225         case AddressTableEntry::Receiving:
226             return Receive;
227         default: break;
228         }
229     }
230     return QVariant();
231 }
232 
setData(const QModelIndex & index,const QVariant & value,int role)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             {
249                 editStatus = NO_CHANGES;
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             {
258                 editStatus = INVALID_ADDRESS;
259                 return false;
260             }
261             // Do nothing, if old address == new address
262             else if(newAddress == curAddress)
263             {
264                 editStatus = NO_CHANGES;
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)
269             if (walletModel->wallet().getAddress(
270                     newAddress, /* name= */ nullptr, /* is_mine= */ nullptr, /* purpose= */ nullptr))
271             {
272                 editStatus = DUPLICATE_ADDRESS;
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 
headerData(int section,Qt::Orientation orientation,int role) const289 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 
flags(const QModelIndex & index) const301 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 
index(int row,int column,const QModelIndex & parent) const318 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 
updateEntry(const QString & address,const QString & label,bool isMine,const QString & purpose,int status)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 
addRow(const QString & type,const QString & label,const QString & address,const OutputType address_type)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         {
350             editStatus = INVALID_ADDRESS;
351             return QString();
352         }
353         // Check for duplicate addresses
354         {
355             if (walletModel->wallet().getAddress(
356                     DecodeDestination(strAddress), /* name= */ nullptr, /* is_mine= */ nullptr, /* purpose= */ nullptr))
357             {
358                 editStatus = DUPLICATE_ADDRESS;
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         {
369             WalletModel::UnlockContext ctx(walletModel->requestUnlock());
370             if(!ctx.isValid())
371             {
372                 // Unlock wallet failed or was cancelled
373                 editStatus = WALLET_UNLOCK_FAILURE;
374                 return QString();
375             }
376             if(!walletModel->wallet().getKeyFromPool(false /* internal */, newKey))
377             {
378                 editStatus = KEY_GENERATION_FAILURE;
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 
removeRows(int row,int count,const QModelIndex & parent)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 
labelForAddress(const QString & address) const410 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 
purposeForAddress(const QString & address) const419 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 
getAddressData(const QString & address,std::string * name,std::string * purpose) const428 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 
lookupAddress(const QString & address) const435 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 
GetDefaultAddressType() const449 OutputType AddressTableModel::GetDefaultAddressType() const { return walletModel->wallet().getDefaultAddressType(); };
450 
emitDataChanged(int idx)451 void AddressTableModel::emitDataChanged(int idx)
452 {
453     Q_EMIT dataChanged(index(idx, 0, QModelIndex()), index(idx, columns.length()-1, QModelIndex()));
454 }
455