addresstablemodel.cpp
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 22 struct AddressTableEntry 23 { 24 enum Type { 25 Sending, 26 Receiving, 27 Hidden /* QSortFilterProxyModel will filter these out */ 28 }; 29 30 Type type; 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 39 struct AddressTableEntryLessThan 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 constexpr AddressTableEntry::Type translateTransactionType(wallet::AddressPurpose purpose, bool isMine) 57 { 58 // "refund" addresses aren't shown, and change addresses aren't returned by getAddresses at all. 59 switch (purpose) { 60 case wallet::AddressPurpose::SEND: return AddressTableEntry::Sending; 61 case wallet::AddressPurpose::RECEIVE: return AddressTableEntry::Receiving; 62 case wallet::AddressPurpose::REFUND: return AddressTableEntry::Hidden; 63 } // no default case, so the compiler can warn about missing cases 64 assert(false); 65 } 66 67 // Private implementation 68 class AddressTablePriv 69 { 70 public: 71 QList<AddressTableEntry> cachedAddressTable; 72 AddressTableModel *parent; 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 } 86 AddressTableEntry::Type addressType = translateTransactionType( 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. 96 std::sort(cachedAddressTable.begin(), cachedAddressTable.end(), AddressTableEntryLessThan()); 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( 103 cachedAddressTable.begin(), cachedAddressTable.end(), address, AddressTableEntryLessThan()); 104 QList<AddressTableEntry>::iterator upper = std::upper_bound( 105 cachedAddressTable.begin(), cachedAddressTable.end(), address, AddressTableEntryLessThan()); 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 151 AddressTableEntry *index(int idx) 152 { 153 if(idx >= 0 && idx < cachedAddressTable.size()) 154 { 155 return &cachedAddressTable[idx]; 156 } 157 else 158 { 159 return nullptr; 160 } 161 } 162 }; 163 164 AddressTableModel::AddressTableModel(WalletModel *parent, bool pk_hash_only) : 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 172 AddressTableModel::~AddressTableModel() 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 { 224 case AddressTableEntry::Sending: 225 return Send; 226 case AddressTableEntry::Receiving: 227 return Receive; 228 case AddressTableEntry::Hidden: 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()); 241 wallet::AddressPurpose purpose = rec->type == AddressTableEntry::Sending ? wallet::AddressPurpose::SEND : wallet::AddressPurpose::RECEIVE; 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 { 252 editStatus = NO_CHANGES; 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 { 261 editStatus = INVALID_ADDRESS; 262 return false; 263 } 264 // Do nothing, if old address == new address 265 else if(newAddress == curAddress) 266 { 267 editStatus = NO_CHANGES; 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) 272 if (walletModel->wallet().getAddress( 273 newAddress, /* name= */ nullptr, /* is_mine= */ nullptr, /* purpose= */ nullptr)) 274 { 275 editStatus = DUPLICATE_ADDRESS; 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 { 353 editStatus = INVALID_ADDRESS; 354 return QString(); 355 } 356 // Check for duplicate addresses 357 { 358 if (walletModel->wallet().getAddress( 359 DecodeDestination(strAddress), /* name= */ nullptr, /* is_mine= */ nullptr, /* purpose= */ nullptr)) 360 { 361 editStatus = DUPLICATE_ADDRESS; 362 return QString(); 363 } 364 } 365 366 // Add entry 367 walletModel->wallet().setAddressBook(DecodeDestination(strAddress), strLabel, wallet::AddressPurpose::SEND); 368 } 369 else if(type == Receive) 370 { 371 // Generate a new address to associate with given label 372 auto op_dest = walletModel->wallet().getNewDestination(address_type, strLabel); 373 if (!op_dest) { 374 WalletModel::UnlockContext ctx(walletModel->requestUnlock()); 375 if (!ctx.isValid()) { 376 // Unlock wallet failed or was cancelled 377 editStatus = WALLET_UNLOCK_FAILURE; 378 return QString(); 379 } 380 op_dest = walletModel->wallet().getNewDestination(address_type, strLabel); 381 if (!op_dest) { 382 editStatus = KEY_GENERATION_FAILURE; 383 return QString(); 384 } 385 } 386 strAddress = EncodeDestination(*op_dest); 387 } 388 else 389 { 390 return QString(); 391 } 392 return QString::fromStdString(strAddress); 393 } 394 395 bool AddressTableModel::removeRows(int row, int count, const QModelIndex &parent) 396 { 397 Q_UNUSED(parent); 398 AddressTableEntry *rec = priv->index(row); 399 if(count != 1 || !rec || rec->type == AddressTableEntry::Receiving) 400 { 401 // Can only remove one row at a time, and cannot remove rows not in model. 402 // Also refuse to remove receiving addresses. 403 return false; 404 } 405 walletModel->wallet().delAddressBook(DecodeDestination(rec->address.toStdString())); 406 return true; 407 } 408 409 QString AddressTableModel::labelForAddress(const QString &address) const 410 { 411 std::string name; 412 if (getAddressData(address, &name, /* purpose= */ nullptr)) { 413 return QString::fromStdString(name); 414 } 415 return QString(); 416 } 417 418 std::optional<wallet::AddressPurpose> AddressTableModel::purposeForAddress(const QString &address) const 419 { 420 wallet::AddressPurpose purpose; 421 if (getAddressData(address, /* name= */ nullptr, &purpose)) { 422 return purpose; 423 } 424 return std::nullopt; 425 } 426 427 bool AddressTableModel::getAddressData(const QString &address, 428 std::string* name, 429 wallet::AddressPurpose* purpose) const { 430 CTxDestination destination = DecodeDestination(address.toStdString()); 431 return walletModel->wallet().getAddress(destination, name, /* is_mine= */ nullptr, purpose); 432 } 433 434 int AddressTableModel::lookupAddress(const QString &address) const 435 { 436 QModelIndexList lst = match(index(0, Address, QModelIndex()), 437 Qt::EditRole, address, 1, Qt::MatchExactly); 438 if(lst.isEmpty()) 439 { 440 return -1; 441 } 442 else 443 { 444 return lst.at(0).row(); 445 } 446 } 447 448 OutputType AddressTableModel::GetDefaultAddressType() const { return walletModel->wallet().getDefaultAddressType(); }; 449 450 void AddressTableModel::emitDataChanged(int idx) 451 { 452 Q_EMIT dataChanged(index(idx, 0, QModelIndex()), index(idx, columns.length()-1, QModelIndex())); 453 } 454 455 QString AddressTableModel::GetWalletDisplayName() const { return walletModel->getDisplayName(); };