addresstablemodel.cpp
1 // Copyright (c) 2011-present 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, /*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, /*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 if (auto dest{walletModel->wallet().getNewDestination(address_type, strLabel)}) { 373 strAddress = EncodeDestination(*dest); 374 } else { 375 WalletModel::UnlockContext ctx(walletModel->requestUnlock()); 376 if (!ctx.isValid()) { 377 // Unlock wallet failed or was cancelled 378 editStatus = WALLET_UNLOCK_FAILURE; 379 return QString(); 380 } 381 if (auto dest_retry{walletModel->wallet().getNewDestination(address_type, strLabel)}) { 382 strAddress = EncodeDestination(*dest_retry); 383 } else { 384 editStatus = KEY_GENERATION_FAILURE; 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 } 406 walletModel->wallet().delAddressBook(DecodeDestination(rec->address.toStdString())); 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, 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 449 OutputType AddressTableModel::GetDefaultAddressType() const { return walletModel->wallet().getDefaultAddressType(); }; 450 451 void AddressTableModel::emitDataChanged(int idx) 452 { 453 Q_EMIT dataChanged(index(idx, 0, QModelIndex()), index(idx, columns.length()-1, QModelIndex())); 454 } 455 456 QString AddressTableModel::GetWalletDisplayName() const { return walletModel->getDisplayName(); };