/* * This file is part of the Flowee project * Copyright (C) 2021 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "WalletSecretsModel.h" #include "Wallet.h" #include "FloweePay.h" #include #include WalletSecretsModel::WalletSecretsModel(Wallet *wallet, QObject *parent) : QAbstractListModel(parent), m_wallet(wallet) { assert(m_wallet); update(); connect (wallet, SIGNAL(appendedTransactions(int,int)), this, SLOT(transactionsAddedToWallet(int,int))); } int WalletSecretsModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) // only for the (invalid) root node we return a count, since this is a list not a tree return 0; return m_selectedPrivates.size(); } QVariant WalletSecretsModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); QMutexLocker locker(&m_wallet->m_lock); assert(m_selectedPrivates.size() > index.row()); auto foundItem = m_wallet->m_walletSecrets.find(m_selectedPrivates.at(index.row())); assert(foundItem != m_wallet->m_walletSecrets.end()); const auto &item = foundItem->second; switch (role) { case BitcoinAddress: { CashAddress::Content c; c.type = CashAddress::PUBKEY_TYPE; c.hash = std::vector(item.address.begin(), item.address.end()); auto s = CashAddress::encodeCashAddr(chainPrefix(), c); return QString::fromStdString(s).mid(chainPrefix().size() + 1); } case PrivateKey: { CBase58Data d; d.setData(item.privKey, FloweePay::instance()->chain() == P2PNet::MainChain ? CBase58Data::Mainnet : CBase58Data::Testnet); return QVariant(QString::fromStdString(d.ToString())); } case FromChangeChain: return QVariant(item.fromChangeChain); case HDIndex: return QVariant(item.hdDerivationIndex); case Balance: return QVariant(details(foundItem->first).saldo); case HistoricalCoins: return QVariant(details(foundItem->first).historicalCoins); case NumCoins: return QVariant(details(foundItem->first).coins); case UsedSchnorr: return QVariant(item.signatureType == Wallet::SignedAsSchnorr); } return QVariant(); } QHash WalletSecretsModel::roleNames() const { QHash answer; answer[BitcoinAddress] = "address"; answer[PrivateKey] = "privatekey"; answer[FromChangeChain] = "isChange"; answer[HDIndex] = "hdIndex"; answer[Balance] = "balance"; answer[HistoricalCoins] = "historicalCoinCount"; answer[NumCoins] = "numCoins"; answer[UsedSchnorr] = "usedSchnorr"; return answer; } bool WalletSecretsModel::showChangeChain() const { return m_showChangeChain; } void WalletSecretsModel::setShowChangeChain(bool on) { if (m_showChangeChain == on) return; m_showChangeChain = on; emit showChangeChainChanged(); update(); } bool WalletSecretsModel::showUsedAddresses() const { return m_showUsedAddresses; } void WalletSecretsModel::setShowUsedAddresses(bool on) { if (m_showUsedAddresses == on) return; m_showUsedAddresses = on; emit showUsedAddressesChanged(); update(); } const QString &WalletSecretsModel::searchTerm() const { return m_search; } void WalletSecretsModel::search(const QString &newSearch) { if (m_search == newSearch) return; m_search = newSearch; update(); emit searchChanged(); } void WalletSecretsModel::update() { assert(m_wallet); QMutexLocker locker(&m_wallet->m_lock); if (!m_selectedPrivates.isEmpty()) { beginRemoveRows(QModelIndex(), 0, m_selectedPrivates.size() - 1); m_selectedPrivates.clear(); endRemoveRows(); } auto iter = m_wallet->m_walletSecrets.begin(); while (iter != m_wallet->m_walletSecrets.end()) { const auto &secret = iter->second; bool use = !secret.fromHdWallet || m_showChangeChain == secret.fromChangeChain; if (use && !m_wallet->isSingleAddressWallet()) { /* * Addresses (a) have money currently on them, (b) have had money * on them in the past (coins: 0/1), or (c) have never had any deposits made. * * The 'm_showUsedAddreses', in english, means that we hide empty, previously used addresses: * true = coins: 0/n (where n > 0) * false = coins 0/0 or n/m (where n > 0) */ auto keyDetails = m_wallet->fetchKeyDetails(iter->first); const bool addressWasUsed = keyDetails.historicalCoins > 0 && keyDetails.coins == 0; use = addressWasUsed == m_showUsedAddresses; } if (use) m_selectedPrivates.append(iter->first); ++iter; } beginInsertRows(QModelIndex(), 0, m_selectedPrivates.size() - 1); endInsertRows(); } const Wallet::KeyDetails &WalletSecretsModel::details(int privKeyId) const { QMutexLocker locker(&m_lock); auto iter = m_keyDetails.find(privKeyId); if (iter == m_keyDetails.end()) { assert(m_wallet); auto rc = m_keyDetails.insert(std::make_pair(privKeyId, m_wallet->fetchKeyDetails(privKeyId))); return rc.first->second; } return iter->second; } void WalletSecretsModel::transactionsAddedToWallet(int from, int count) { assert(m_wallet); auto iter = m_wallet->m_walletTransactions.find(from); std::set changedPrivateKeys; while ( count-- > 0 && iter != m_wallet->m_walletTransactions.end()) { const auto &tx = iter->second; for (auto input = tx.inputToWTX.begin(); input != tx.inputToWTX.end(); ++input) { Wallet::OutputRef ref(input->second); auto w = m_wallet->m_walletTransactions.find(ref.txIndex()); assert(w != m_wallet->m_walletTransactions.end()); auto prevOut = w->second.outputs.find(ref.outputIndex()); assert(prevOut != w->second.outputs.end()); changedPrivateKeys.insert(prevOut->second.walletSecretId); } for (auto out = tx.outputs.begin(); out != tx.outputs.end(); ++out) { changedPrivateKeys.insert(out->second.walletSecretId); } ++iter; } logDebug() << "New transactions touched these private keys:" << changedPrivateKeys; for (auto privKeyId : changedPrivateKeys) { auto detailsIter = m_keyDetails.find(privKeyId); if (detailsIter != m_keyDetails.end()) { m_keyDetails.erase(detailsIter); // remove cached version. // make UI re-request the data for (int i = 0; i < m_selectedPrivates.size(); ++i) { if (m_selectedPrivates.at(i) == privKeyId) { beginRemoveRows(QModelIndex(), i, i); endRemoveRows(); beginInsertRows(QModelIndex(), i, i); endInsertRows(); break; } } } } }