From e3ac42adfcc8c02eed8931e53731cc3c4445e19e Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Fri, 18 Oct 2019 11:26:12 -0700 Subject: [PATCH] Add transactions fetch --- src/controller.cpp | 124 ++++++++++++++++---------------------- src/controller.h | 2 - src/liteinterface.cpp | 8 +-- src/liteinterface.h | 15 ++++- src/mainwindow.cpp | 12 ---- src/senttxstore.cpp | 115 ------------------------------------ src/senttxstore.h | 20 ------- src/txtablemodel.cpp | 134 ++++++++++++++++++++++-------------------- src/txtablemodel.h | 10 +--- zecwallet-lite.pro | 2 - 10 files changed, 136 insertions(+), 306 deletions(-) delete mode 100644 src/senttxstore.cpp delete mode 100644 src/senttxstore.h diff --git a/src/controller.cpp b/src/controller.cpp index 74fcf9e..afef9b6 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -2,7 +2,6 @@ #include "addressbook.h" #include "settings.h" -#include "senttxstore.h" #include "version.h" #include "websockets.h" @@ -143,9 +142,7 @@ void Controller::noConnection() { // Clear Transactions table. QList emptyTxs; - transactionsTableModel->addTData(emptyTxs); - transactionsTableModel->addZRecvData(emptyTxs); - transactionsTableModel->addZSentData(emptyTxs); + transactionsTableModel->replaceData(emptyTxs); // Clear balances ui->balSheilded->setText(""); @@ -160,28 +157,6 @@ void Controller::noConnection() { ui->inputsCombo->clear(); } -// Refresh received z txs by calling z_listreceivedbyaddress/gettransaction -void Controller::refreshReceivedZTrans(QList zaddrs) { - if (!zrpc->haveConnection()) - return noConnection(); - - // We'll only refresh the received Z txs if settings allows us. - if (!Settings::getInstance()->getSaveZtxs()) { - QList emptylist; - transactionsTableModel->addZRecvData(emptylist); - return; - } - - zrpc->fetchReceivedZTrans(zaddrs, - [=] (QString addr) { - model->markAddressUsed(addr); - }, - [=] (QList txdata) { - transactionsTableModel->addZRecvData(txdata); - } - ); -} - /// This will refresh all the balance data from zcashd void Controller::refresh(bool force) { if (!zrpc->haveConnection()) @@ -190,7 +165,6 @@ void Controller::refresh(bool force) { getInfoThenRefresh(force); } - void Controller::getInfoThenRefresh(bool force) { if (!zrpc->haveConnection()) return noConnection(); @@ -275,8 +249,7 @@ void Controller::refreshAddresses() { model->replaceTaddresses(newtaddresses); // Refresh the sent and received txs from all these z-addresses - refreshSentZTrans(); - refreshReceivedZTrans(model->getAllZAddresses()); + refreshTransactions(); }); } @@ -362,57 +335,64 @@ void Controller::refreshTransactions() { zrpc->fetchTransactions([=] (json reply) { QList txdata; + for (auto& it : reply.get()) { - double fee = 0; - if (!it["fee"].is_null()) { - fee = it["fee"].get(); - } - - QString address = (it["address"].is_null() ? "" : QString::fromStdString(it["address"])); - - TransactionItem tx{ - QString::fromStdString(it["category"]), - (qint64)it["time"].get(), - address, - QString::fromStdString(it["txid"]), - it["amount"].get() + fee, - static_cast(it["confirmations"].get()), - "", "" }; + QString address; + qint64 total_amount; + QList items; - txdata.push_back(tx); - if (!address.isEmpty()) - model->markAddressUsed(address); - } - - // Update model data, which updates the table view - transactionsTableModel->addTData(txdata); - }); -} + // First, check if there's outgoing metadata + if (!it["outgoing_metadata"].is_null()) { + + for (auto o: it["outgoing_metadata"].get()) { + QString address = QString::fromStdString(o["address"]); + qint64 amount = o["value"].get(); + + QString memo; + if (!o["memo"].is_null()) { + memo = QString::fromStdString(o["memo"]); + } -// Read sent Z transactions from the file. -void Controller::refreshSentZTrans() { - if (!zrpc->haveConnection()) - return noConnection(); + items.push_back(TransactionItemDetail{address, amount, memo}); + total_amount += amount; + } - auto sentZTxs = SentTxStore::readSentTxFile(); + if (items.length() == 1) { + address = items[0].address; + } else { + address = "(Multiple)"; + } - // If there are no sent z txs, then empty the table. - // This happens when you clear history. - if (sentZTxs.isEmpty()) { - transactionsTableModel->addZSentData(sentZTxs); - return; - } + txdata.push_back(TransactionItem{ + "Sent", + it["block_height"].get(), + address, + QString::fromStdString(it["txid"]), + 1, + items + }); + } else { + // Incoming Transaction + address = (it["address"].is_null() ? "" : QString::fromStdString(it["address"])); + model->markAddressUsed(address); - QList txids; + TransactionItem tx{ + "Receive", + it["block_height"].get(), + address, + QString::fromStdString(it["txid"]), + 1, + items + }; - for (auto sentTx: sentZTxs) { - txids.push_back(sentTx.txid); - } + txdata.push_back(tx); + } + + } - // Look up all the txids to get the confirmation count for them. - zrpc->fetchReceivedTTrans(txids, sentZTxs, [=](auto newSentZTxs) { - transactionsTableModel->addZSentData(newSentZTxs); + // Update model data, which updates the table view + transactionsTableModel->replaceData(txdata); }); } @@ -485,8 +465,6 @@ void Controller::watchTxStatus() { if (status == "success") { auto txid = QString::fromStdString(it["result"]["txid"]); - - SentTxStore::addToSentTx(watchingOps[id].tx, txid); auto wtx = watchingOps[id]; watchingOps.remove(id); diff --git a/src/controller.h b/src/controller.h index 5c9c43b..39cb4b1 100644 --- a/src/controller.h +++ b/src/controller.h @@ -86,8 +86,6 @@ private: void refreshBalances(); void refreshTransactions(); - void refreshSentZTrans(); - void refreshReceivedZTrans(QList zaddresses); bool processUnspent (const json& reply, QMap* newBalances, QList* newUtxos); void updateUI (bool anyUnconfirmed); diff --git a/src/liteinterface.cpp b/src/liteinterface.cpp index 97fd9f4..c5fce3a 100644 --- a/src/liteinterface.cpp +++ b/src/liteinterface.cpp @@ -155,13 +155,7 @@ void LiteInterface::fetchTransactions(const std::function& cb) { if (conn == nullptr) return; - // json payload = { - // {"jsonrpc", "1.0"}, - // {"id", "someid"}, - // {"method", "listtransactions"} - // }; - - // conn->doRPCWithDefaultErrorHandling(payload, cb); + conn->doRPCWithDefaultErrorHandling("list", "", cb); } void LiteInterface::sendZTransaction(json params, const std::function& cb, diff --git a/src/liteinterface.h b/src/liteinterface.h index 96e1fac..b52fb6d 100644 --- a/src/liteinterface.h +++ b/src/liteinterface.h @@ -7,15 +7,24 @@ using json = nlohmann::json; +// Since each transaction can contain multiple outputs, we separate them out individually +// into a struct with address, amount, memo +struct TransactionItemDetail { + QString address; + qint64 amount; + QString memo; +}; + +// Represents a row in the transactions table. Note that each transaction can contain +// multiple addresses (i.e., Multiple TransctionItemDetail) struct TransactionItem { QString type; qint64 datetime; QString address; QString txid; - double amount; long confirmations; - QString fromAddr; - QString memo; + + QList items; }; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index e08faaa..4ca4458 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -16,7 +16,6 @@ #include "balancestablemodel.h" #include "settings.h" #include "version.h" -#include "senttxstore.h" #include "connection.h" #include "requestdialog.h" #include "websockets.h" @@ -275,17 +274,6 @@ void MainWindow::setupSettingsModal() { Settings::getInstance()->setSaveZtxs(checked); }); - // Setup clear button - QObject::connect(settings.btnClearSaved, &QCheckBox::clicked, [=]() { - if (QMessageBox::warning(this, "Clear saved history?", - "Shielded z-Address transactions are stored locally in your wallet, outside zcashd. You may delete this saved information safely any time for your privacy.\nDo you want to delete the saved shielded transactions now?", - QMessageBox::Yes, QMessageBox::Cancel)) { - SentTxStore::deleteHistory(); - // Reload after the clear button so existing txs disappear - rpc->refresh(true); - } - }); - // Setup theme combo int theme_index = settings.comboBoxTheme->findText(Settings::getInstance()->get_theme_name(), Qt::MatchExactly); settings.comboBoxTheme->setCurrentIndex(theme_index); diff --git a/src/senttxstore.cpp b/src/senttxstore.cpp deleted file mode 100644 index 5341664..0000000 --- a/src/senttxstore.cpp +++ /dev/null @@ -1,115 +0,0 @@ -#include "senttxstore.h" -#include "settings.h" - -/// Get the location of the app data file to be written. -QString SentTxStore::writeableFile() { - auto filename = QStringLiteral("senttxstore.dat"); - - auto dir = QDir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)); - if (!dir.exists()) - QDir().mkpath(dir.absolutePath()); - - if (Settings::getInstance()->isTestnet()) { - return dir.filePath("testnet-" % filename); - } else { - return dir.filePath(filename); - } -} - -// delete the sent history. -void SentTxStore::deleteHistory() { - QFile data(writeableFile()); - data.remove(); - data.close(); -} - -QList SentTxStore::readSentTxFile() { - QFile data(writeableFile()); - if (!data.exists()) { - return QList(); - } - - QJsonDocument jsonDoc; - - data.open(QFile::ReadOnly); - jsonDoc = QJsonDocument::fromJson(data.readAll()); - data.close(); - - QList items; - - for (auto i : jsonDoc.array()) { - auto sentTx = i.toObject(); - TransactionItem t{"send", (qint64)sentTx["datetime"].toVariant().toLongLong(), - sentTx["address"].toString(), - sentTx["txid"].toString(), - sentTx["amount"].toDouble() + sentTx["fee"].toDouble(), - 0, sentTx["from"].toString(), ""}; - items.push_back(t); - } - - return items; -} - -void SentTxStore::addToSentTx(Tx tx, QString txid) { - // Save transactions only if the settings are allowed - if (!Settings::getInstance()->getSaveZtxs()) - return; - - // Also, only store outgoing txs where the from address is a z-Addr. Else, regular zcashd - // stores it just fine - if (! Settings::isZAddress(tx.fromAddr)) - return; - - QFile data(writeableFile()); - QJsonDocument jsonDoc; - - // If data doesn't exist, then create a blank one - if (!data.exists()) { - QJsonArray a; - jsonDoc.setArray(a); - - QFile newFile(writeableFile()); - newFile.open(QFile::WriteOnly); - newFile.write(jsonDoc.toJson()); - newFile.close(); - } else { - data.open(QFile::ReadOnly); - jsonDoc = QJsonDocument().fromJson(data.readAll()); - data.close(); - } - - // Calculate total amount in this tx - double totalAmount = 0; - for (auto i : tx.toAddrs) { - totalAmount += i.amount; - } - - QString toAddresses; - if (tx.toAddrs.length() == 1) { - toAddresses = tx.toAddrs[0].addr; - } else { - // Concatenate all the toAddresses - for (auto a : tx.toAddrs) { - toAddresses += a.addr % "(" % Settings::getZECDisplayFormat(a.amount) % ") "; - } - } - - auto list = jsonDoc.array(); - QJsonObject txItem; - txItem["type"] = "sent"; - txItem["from"] = tx.fromAddr; - txItem["datetime"] = QDateTime::currentMSecsSinceEpoch() / (qint64)1000; - txItem["address"] = toAddresses; - txItem["txid"] = txid; - txItem["amount"] = -totalAmount; - txItem["fee"] = -tx.fee; - list.append(txItem); - - jsonDoc.setArray(list); - - QFile writer(writeableFile()); - if (writer.open(QFile::WriteOnly | QFile::Truncate)) { - writer.write(jsonDoc.toJson()); - } - writer.close(); -} diff --git a/src/senttxstore.h b/src/senttxstore.h deleted file mode 100644 index 9093a00..0000000 --- a/src/senttxstore.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef SENTTXSTORE_H -#define SENTTXSTORE_H - -#include "precompiled.h" -#include "mainwindow.h" -#include "controller.h" - -class SentTxStore { -public: - static void deleteHistory(); - - static QList readSentTxFile(); - static void addToSentTx(Tx tx, QString txid); - -private: - static QString writeableFile(); - -}; - -#endif // SENTTXSTORE_H diff --git a/src/txtablemodel.cpp b/src/txtablemodel.cpp index 395d82f..9c4d609 100644 --- a/src/txtablemodel.cpp +++ b/src/txtablemodel.cpp @@ -9,34 +9,15 @@ TxTableModel::TxTableModel(QObject *parent) TxTableModel::~TxTableModel() { delete modeldata; - delete tTrans; - delete zsTrans; - delete zrTrans; } -void TxTableModel::addZSentData(const QList& data) { - delete zsTrans; - zsTrans = new QList(); - std::copy(data.begin(), data.end(), std::back_inserter(*zsTrans)); - - updateAllData(); -} - -void TxTableModel::addZRecvData(const QList& data) { - delete zrTrans; - zrTrans = new QList(); - std::copy(data.begin(), data.end(), std::back_inserter(*zrTrans)); - - updateAllData(); -} - - -void TxTableModel::addTData(const QList& data) { - delete tTrans; - tTrans = new QList(); - std::copy(data.begin(), data.end(), std::back_inserter(*tTrans)); +void TxTableModel::replaceData(const QList& data) { + delete modeldata; + modeldata = new QList(); + std::copy(data.begin(), data.end(), std::back_inserter(*modeldata)); - updateAllData(); + dataChanged(index(0, 0), index(modeldata->size()-1, columnCount(index(0,0))-1)); + layoutChanged(); } bool TxTableModel::exportToCsv(QString fileName) const { @@ -61,8 +42,9 @@ bool TxTableModel::exportToCsv(QString fileName) const { for (int col = 0; col < headers.length(); col++) { out << "\"" << data(index(row, col), Qt::DisplayRole).toString() << "\","; } + // Memo - out << "\"" << modeldata->at(row).memo << "\""; + out << "\"" << this->getMemo(row) << "\""; out << endl; } @@ -70,25 +52,6 @@ bool TxTableModel::exportToCsv(QString fileName) const { return true; } -void TxTableModel::updateAllData() { - auto newmodeldata = new QList(); - - if (tTrans != nullptr) std::copy( tTrans->begin(), tTrans->end(), std::back_inserter(*newmodeldata)); - if (zsTrans != nullptr) std::copy(zsTrans->begin(), zsTrans->end(), std::back_inserter(*newmodeldata)); - if (zrTrans != nullptr) std::copy(zrTrans->begin(), zrTrans->end(), std::back_inserter(*newmodeldata)); - - // Sort by reverse time - std::sort(newmodeldata->begin(), newmodeldata->end(), [=] (auto a, auto b) { - return a.datetime > b.datetime; // reverse sort - }); - - // And then swap out the modeldata with the new one. - delete modeldata; - modeldata = newmodeldata; - - dataChanged(index(0, 0), index(modeldata->size()-1, columnCount(index(0,0))-1)); - layoutChanged(); -} int TxTableModel::rowCount(const QModelIndex&) const { @@ -135,18 +98,31 @@ void TxTableModel::updateAllData() { } case Column::Time: return QDateTime::fromMSecsSinceEpoch(dat.datetime * (qint64)1000).toLocalTime().toString(); case Column::Confirmations: return QString::number(dat.confirmations); - case Column::Amount: return Settings::getZECDisplayFormat(dat.amount); + case Column::Amount: { + // Sum up all the amounts + qint64 total = 0; + for (int i=0; i < dat.items.length(); i++) { + total += dat.items[i].amount; + } + return Settings::getZECDisplayFormat(total); + } } } if (role == Qt::ToolTipRole) { switch (index.column()) { case Column::Type: { - if (dat.memo.startsWith("zcash:")) { - return Settings::paymentURIPretty(Settings::parseURI(dat.memo)); + // If there are multiple memos, then mark them as such + if (dat.items.length() == 1) { + auto memo = dat.items[0].memo; + if (memo.startsWith("zcash:")) { + return Settings::paymentURIPretty(Settings::parseURI(memo)); + } else { + return modeldata->at(index.row()).type + + (memo.isEmpty() ? "" : " tx memo: \"" + memo + "\""); + } } else { - return modeldata->at(index.row()).type + - (dat.memo.isEmpty() ? "" : " tx memo: \"" + dat.memo + "\""); + return "Multiple"; } } case Column::Address: { @@ -158,21 +134,33 @@ void TxTableModel::updateAllData() { } case Column::Time: return QDateTime::fromMSecsSinceEpoch(modeldata->at(index.row()).datetime * (qint64)1000).toLocalTime().toString(); case Column::Confirmations: return QString("%1 Network Confirmations").arg(QString::number(dat.confirmations)); - case Column::Amount: return Settings::getInstance()->getUSDFromZecAmount(modeldata->at(index.row()).amount); + case Column::Amount: { + // Sum up all the amounts + qint64 total = 0; + for (int i=0; i < dat.items.length(); i++) { + total += dat.items[i].amount; + } + return Settings::getInstance()->getUSDFromZecAmount(total); } + } } if (role == Qt::DecorationRole && index.column() == 0) { - if (!dat.memo.isEmpty()) { - // If the memo is a Payment URI, then show a payment request icon - if (dat.memo.startsWith("zcash:")) { - QIcon icon(":/icons/res/paymentreq.gif"); - return QVariant(icon.pixmap(16, 16)); - } else { - // Return the info pixmap to indicate memo - QIcon icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxInformation); - return QVariant(icon.pixmap(16, 16)); - } + bool hasMemo = false; + for (int i=0; i < dat.items.length(); i++) { + if (!dat.items[i].memo.isEmpty()) { + hasMemo = true; + } + } + + // If the memo is a Payment URI, then show a payment request icon + if (dat.items.length() == 1 && dat.items[0].memo.startsWith("zcash:")) { + QIcon icon(":/icons/res/paymentreq.gif"); + return QVariant(icon.pixmap(16, 16)); + } else if (hasMemo) { + // Return the info pixmap to indicate memo + QIcon icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxInformation); + return QVariant(icon.pixmap(16, 16)); } else { // Empty pixmap to make it align QPixmap p(16, 16); @@ -208,7 +196,21 @@ QString TxTableModel::getTxId(int row) const { } QString TxTableModel::getMemo(int row) const { - return modeldata->at(row).memo; + auto dat = modeldata->at(row); + bool hasMemo = false; + for (int i=0; i < dat.items.length(); i++) { + if (!dat.items[i].memo.isEmpty()) { + hasMemo = true; + } + } + + if (dat.items.length() == 1) { + return dat.items[0].memo; + } else if (hasMemo) { + return "(Multiple)"; + } else { + return ""; + } } qint64 TxTableModel::getConfirmations(int row) const { @@ -228,5 +230,11 @@ QString TxTableModel::getType(int row) const { } QString TxTableModel::getAmt(int row) const { - return Settings::getDecimalString(modeldata->at(row).amount); + auto dat = modeldata->at(row); + + qint64 total = 0; + for (int i=0; i < dat.items.length(); i++) { + total += dat.items[i].amount; + } + return Settings::getDecimalString(total); } diff --git a/src/txtablemodel.h b/src/txtablemodel.h index f295bc5..8e60ea7 100644 --- a/src/txtablemodel.h +++ b/src/txtablemodel.h @@ -20,9 +20,7 @@ public: Amount = 4 }; - void addTData (const QList& data); - void addZSentData(const QList& data); - void addZRecvData(const QList& data); + void replaceData (const QList& data); QString getTxId(int row) const; QString getMemo(int row) const; @@ -40,12 +38,6 @@ public: QVariant headerData(int section, Qt::Orientation orientation, int role) const; private: - void updateAllData(); - - QList* tTrans = nullptr; - QList* zrTrans = nullptr; // Z received - QList* zsTrans = nullptr; // Z sent - QList* modeldata = nullptr; QList headers; diff --git a/zecwallet-lite.pro b/zecwallet-lite.pro index a35333c..1a54595 100644 --- a/zecwallet-lite.pro +++ b/zecwallet-lite.pro @@ -44,7 +44,6 @@ SOURCES += \ src/3rdparty/qrcode/QrSegment.cpp \ src/settings.cpp \ src/sendtab.cpp \ - src/senttxstore.cpp \ src/txtablemodel.cpp \ src/qrcodelabel.cpp \ src/connection.cpp \ @@ -73,7 +72,6 @@ HEADERS += \ src/3rdparty/json/json.hpp \ src/settings.h \ src/txtablemodel.h \ - src/senttxstore.h \ src/qrcodelabel.h \ src/connection.h \ src/fillediconlabel.h \