/* * 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 "WalletCoinsModel.h" #include "Wallet.h" #include "FloweePay.h" #include #include #include WalletCoinsModel::WalletCoinsModel(Wallet *wallet, QObject *parent) : QAbstractListModel(parent) { setWallet(wallet); } void WalletCoinsModel::setWallet(Wallet *newWallet) { assert(newWallet); assert(newWallet->segment()); if (m_wallet == newWallet) return; if (m_wallet) disconnect (m_wallet, SIGNAL(utxosChanged()), this, SLOT(utxosChanged())); m_wallet = newWallet; connect (m_wallet, SIGNAL(utxosChanged()), this, SLOT(utxosChanged())); beginRemoveRows(QModelIndex(), 0, m_rowsToOutputRefs.size() - 1); endRemoveRows(); createMap(); beginInsertRows(QModelIndex(), 0, m_rowsToOutputRefs.size() - 1); endInsertRows(); } int WalletCoinsModel::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_rowsToOutputRefs.size(); } QVariant WalletCoinsModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); auto iter = m_rowsToOutputRefs.find(index.row()); if (iter == m_rowsToOutputRefs.end()) { logDebug() << "This looks wrong"; return QVariant(); } Wallet::OutputRef outRef(iter->second); QMutexLocker locker(&m_wallet->m_lock); switch (role) { case Value: { const auto utxo = m_wallet->m_unspentOutputs.find(iter->second); if (utxo != m_wallet->m_unspentOutputs.end()) return QVariant(static_cast(utxo->second)); break; } case Age: { const auto tx = m_wallet->m_walletTransactions.find(outRef.txIndex()); if (tx != m_wallet->m_walletTransactions.end()) { const int bh = tx->second.minedBlockHeight; if (bh < 1) return tr("Unconfirmed"); const int now = FloweePay::instance()->p2pNet()->blockHeight(); const auto diff = now - bh; if (diff < 76) { const int hours = diff / 6; return tr("%1 hours", "age, like: hours old", hours + 1).arg(hours + 1); } const int days = (diff - 20) / 144; if (days < 10) return tr("%1 days", "age, like: days old", days + 1).arg(days + 1); if (days < 6 * 7) { const int weeks = (days - 2) / 7; return tr("%1 weeks", "age, like: weeks old", weeks).arg(weeks); } const int months = qRound((days - 2) / 30.4); return tr("%1 months", "age, like: months old", months).arg(months); } break; } case Blockheight: { const auto tx = m_wallet->m_walletTransactions.find(outRef.txIndex()); if (tx != m_wallet->m_walletTransactions.end()) return QVariant(tx->second.minedBlockHeight); break; } case FusedCount: { const auto tx = m_wallet->m_walletTransactions.find(outRef.txIndex()); if (tx != m_wallet->m_walletTransactions.end()) return QVariant(tx->second.isCashFusionTx ? 1 : 0); break; } case Address: { const auto tx = m_wallet->m_walletTransactions.find(outRef.txIndex()); if (tx != m_wallet->m_walletTransactions.end()) { auto out = tx->second.outputs.find(outRef.outputIndex()); assert(out != tx->second.outputs.end()); auto secretIter = m_wallet->m_walletSecrets.find(out->second.walletSecretId); assert(secretIter != m_wallet->m_walletSecrets.end()); const auto &secret = secretIter->second; return QVariant(renderAddress(secret.address)); } break; } case CloakedAddress: { if (m_wallet->isSingleAddressWallet()) return QVariant(m_wallet->name()); const auto tx = m_wallet->m_walletTransactions.find(outRef.txIndex()); if (tx != m_wallet->m_walletTransactions.end()) { auto out = tx->second.outputs.find(outRef.outputIndex()); assert(out != tx->second.outputs.end()); auto secretIter = m_wallet->m_walletSecrets.find(out->second.walletSecretId); assert(secretIter != m_wallet->m_walletSecrets.end()); const auto &secret = secretIter->second; if (secret.fromHdWallet && secret.fromChangeChain) return QVariant(tr("Change #%1").arg(secret.hdDerivationIndex)); } break; } case Locked: { const auto lockedIter = m_wallet->m_lockedOutputs.find(iter->second); return QVariant(lockedIter != m_wallet->m_lockedOutputs.end()); } case Selected: { if (m_selectionGetter) return QVariant(m_selectionGetter(iter->second)); } default: break; } return QVariant(); } QHash WalletCoinsModel::roleNames() const { QHash answer; answer[Value] = "value"; answer[Blockheight] = "blockheight"; answer[Age] = "age"; answer[FusedCount] = "fusedCount"; answer[Address] = "address"; answer[CloakedAddress] = "cloakedAddress"; answer[Locked] = "locked"; answer[Selected] = "selected"; return answer; } uint64_t WalletCoinsModel::outRefForRow(int row) const { auto i = m_rowsToOutputRefs.find(row); if (i == m_rowsToOutputRefs.end()) return 0; return i->second; } void WalletCoinsModel::setOutputLocked(int row, bool lock) { auto i = m_rowsToOutputRefs.find(row); if (i == m_rowsToOutputRefs.end()) return; const Wallet::OutputRef id(i->second); bool changed; if (lock) changed = m_wallet->lockUTXO(id); else changed = m_wallet->unlockUTXO(id); if (changed) { beginRemoveRows(QModelIndex(), row, row); endRemoveRows(); beginInsertRows(QModelIndex(), row, row); endInsertRows(); } } void WalletCoinsModel::setSelectionGetter(const std::function &callback) { m_selectionGetter = callback; } void WalletCoinsModel::updateRow(uint64_t outRef) { for (auto i = m_rowsToOutputRefs.cbegin(); i != m_rowsToOutputRefs.cend(); ++i) { if (i->second == outRef) { updateRow(i->first); break; } } } void WalletCoinsModel::updateRow(int row) { beginRemoveRows(QModelIndex(), row, row); endRemoveRows(); beginInsertRows(QModelIndex(), row, row); endInsertRows(); } void WalletCoinsModel::utxosChanged() { beginRemoveRows(QModelIndex(), 0, m_rowsToOutputRefs.size()); endRemoveRows(); createMap(); beginInsertRows(QModelIndex(), 0, m_rowsToOutputRefs.size()); endInsertRows(); } void WalletCoinsModel::createMap() { QMutexLocker locker(&m_wallet->m_lock); // yes, its a recursive lock m_rowsToOutputRefs.clear(); int index = 0; for (auto i = m_wallet->m_unspentOutputs.crbegin(); i != m_wallet->m_unspentOutputs.rend(); ++i) { auto lockIter = m_wallet->m_lockedOutputs.find(i->first); if (lockIter != m_wallet->m_lockedOutputs.end()) { // its locked, so we check if its locked by a transaction not yet confirmed. // If it is (and the second points to a wallet-transaction-index) we simply // hide this one from view. if (lockIter->second > 0) continue; } m_rowsToOutputRefs.insert(std::make_pair(index++, i->first)); } }