Browse Source

Add sent and received shielded Txs in the transactions tab.

connection
Aditya Kulkarni 6 years ago
parent
commit
284a8641d9
  1. 1
      src/mainwindow.h
  2. 2
      src/precompiled.h
  3. 86
      src/rpc.cpp
  4. 16
      src/rpc.h
  5. 7
      src/sendtab.cpp
  6. 86
      src/senttxstore.cpp
  7. 18
      src/senttxstore.h
  8. 15
      src/transactionitem.h
  9. 62
      src/txtablemodel.cpp
  10. 18
      src/txtablemodel.h
  11. 5
      src/utils.h
  12. 3
      zcash-qt-wallet.pro

1
src/mainwindow.h

@ -21,6 +21,7 @@ struct ToFields {
struct Tx { struct Tx {
QString fromAddr; QString fromAddr;
QList<ToFields> toAddrs; QList<ToFields> toAddrs;
double fee;
}; };
namespace Ui { namespace Ui {

2
src/precompiled.h

@ -35,6 +35,8 @@
#include <QtNetwork/QNetworkAccessManager> #include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkReply> #include <QtNetwork/QNetworkReply>
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include <QAbstractTableModel> #include <QAbstractTableModel>
#include <QAbstractItemModel> #include <QAbstractItemModel>
#include <QObject> #include <QObject>

86
src/rpc.cpp

@ -1,7 +1,7 @@
#include "rpc.h" #include "rpc.h"
#include "utils.h" #include "utils.h"
#include "transactionitem.h"
#include "settings.h" #include "settings.h"
#include "senttxstore.h"
using json = nlohmann::json; using json = nlohmann::json;
@ -35,7 +35,7 @@ RPC::RPC(QNetworkAccessManager* client, MainWindow* main) {
// Set up the timer to watch for tx status // Set up the timer to watch for tx status
txTimer = new QTimer(main); txTimer = new QTimer(main);
QObject::connect(txTimer, &QTimer::timeout, [=]() { QObject::connect(txTimer, &QTimer::timeout, [=]() {
refreshTxStatus(); watchTxStatus();
}); });
// Start at every 10s. When an operation is pending, this will change to every second // Start at every 10s. When an operation is pending, this will change to every second
txTimer->start(Utils::updateSpeed); txTimer->start(Utils::updateSpeed);
@ -45,7 +45,7 @@ RPC::RPC(QNetworkAccessManager* client, MainWindow* main) {
QObject::connect(priceTimer, &QTimer::timeout, [=]() { QObject::connect(priceTimer, &QTimer::timeout, [=]() {
refreshZECPrice(); refreshZECPrice();
}); });
priceTimer->start(60 * 60 * 1000); // Every hour priceTimer->start(Utils::priceRefreshSpeed); // Every hour
} }
RPC::~RPC() { RPC::~RPC() {
@ -389,7 +389,7 @@ void RPC::getReceivedZTrans(QList<QString> zaddrs) {
} }
} }
transactionsTableModel->addNewData(txdata); transactionsTableModel->addZRecvData(txdata);
// Cleanup both responses; // Cleanup both responses;
delete zaddrTxids; delete zaddrTxids;
@ -417,7 +417,7 @@ void RPC::getInfoThenRefresh() {
doRPC(payload, [=] (const json& reply) { doRPC(payload, [=] (const json& reply) {
// Testnet? // Testnet?
if (reply.find("testnet") != reply.end()) { if (!reply["testnet"].is_null()) {
Settings::getInstance()->setTestnet(reply["testnet"].get<json::boolean_t>()); Settings::getInstance()->setTestnet(reply["testnet"].get<json::boolean_t>());
}; };
@ -425,13 +425,11 @@ void RPC::getInfoThenRefresh() {
QIcon i(":/icons/res/connected.png"); QIcon i(":/icons/res/connected.png");
main->statusIcon->setPixmap(i.pixmap(16, 16)); main->statusIcon->setPixmap(i.pixmap(16, 16));
// Expect 2 data additions, then automatically refresh the table
transactionsTableModel->prepNewData(2);
// Refresh everything. // Refresh everything.
refreshBalances(); refreshBalances();
refreshAddresses(); refreshAddresses();
refreshTransactions(); refreshTransactions();
refreshZSentTransactions();
// Call to see if the blockchain is syncing. // Call to see if the blockchain is syncing.
json payload = { json payload = {
@ -559,14 +557,14 @@ void RPC::refreshTransactions() {
for (auto& it : reply.get<json::array_t>()) { for (auto& it : reply.get<json::array_t>()) {
double fee = 0; double fee = 0;
if (it.find("fee") != it.end()) { if (!it["fee"].is_null()) {
fee = it["fee"].get<json::number_float_t>(); fee = it["fee"].get<json::number_float_t>();
} }
TransactionItem tx{ TransactionItem tx{
QString::fromStdString(it["category"]), QString::fromStdString(it["category"]),
it["time"].get<json::number_unsigned_t>(), it["time"].get<json::number_unsigned_t>(),
(it["address"].is_null() ? "(shielded)" : QString::fromStdString(it["address"])), (it["address"].is_null() ? "" : QString::fromStdString(it["address"])),
QString::fromStdString(it["txid"]), QString::fromStdString(it["txid"]),
it["amount"].get<json::number_float_t>() + fee, it["amount"].get<json::number_float_t>() + fee,
it["confirmations"].get<json::number_unsigned_t>() it["confirmations"].get<json::number_unsigned_t>()
@ -576,15 +574,55 @@ void RPC::refreshTransactions() {
} }
// Update model data, which updates the table view // Update model data, which updates the table view
transactionsTableModel->addNewData(txdata); transactionsTableModel->addTData(txdata);
}); });
} }
void RPC::refreshTxStatus(const QString& newOpid) { // Read sent Z transactions from the file.
if (!newOpid.isEmpty()) { void RPC::refreshZSentTransactions() {
watchingOps.insert(newOpid); auto sentZTxs = SentTxStore::readSentTxFile();
QList<QString> txids;
for (auto sentTx: sentZTxs) {
txids.push_back(sentTx.txid);
} }
// Look up all the txids to get the confirmation count for them.
getBatchRPC(txids,
[=] (QString txid) {
json payload = {
{"jsonrpc", "1.0"},
{"id", "senttxid"},
{"method", "gettransaction"},
{"params", {txid.toStdString()}}
};
return payload;
},
[=] (QMap<QString, json>* txidList) {
auto newSentZTxs = sentZTxs;
// Update the original sent list with the confirmation count
// TODO: This whole thing is kinda inefficient. We should probably just update the file
// with the confirmed block number, so we don't have to keep calling gettransaction for the
// sent items.
for (TransactionItem& sentTx: newSentZTxs) {
auto error = txidList->value(sentTx.txid)["confirmations"].is_null();
if (!error)
sentTx.confirmations = txidList->value(sentTx.txid)["confirmations"].get<json::number_unsigned_t>();
}
transactionsTableModel->addZSentData(newSentZTxs);
}
);
}
void RPC::addNewTxToWatch(Tx tx, const QString& newOpid) {
watchingOps.insert(newOpid, tx);
watchTxStatus();
}
void RPC::watchTxStatus() {
// Make an RPC to load pending operation statues // Make an RPC to load pending operation statues
json payload = { json payload = {
{"jsonrpc", "1.0"}, {"jsonrpc", "1.0"},
@ -602,12 +640,13 @@ void RPC::refreshTxStatus(const QString& newOpid) {
QString status = QString::fromStdString(it["status"]); QString status = QString::fromStdString(it["status"]);
if (status == "success") { if (status == "success") {
auto txid = QString::fromStdString(it["result"]["txid"]); auto txid = QString::fromStdString(it["result"]["txid"]);
qDebug() << "Tx completed: " << txid;
SentTxStore::addToSentTx(watchingOps.value(id), txid);
main->ui->statusBar->showMessage(Utils::txidStatusMessage + " " + txid); main->ui->statusBar->showMessage(Utils::txidStatusMessage + " " + txid);
main->loadingLabel->setVisible(false); main->loadingLabel->setVisible(false);
watchingOps.remove(id); watchingOps.remove(id);
txTimer->start(Utils::updateSpeed);
// Refresh balances to show unconfirmed balances // Refresh balances to show unconfirmed balances
refresh(); refresh();
@ -622,17 +661,19 @@ void RPC::refreshTxStatus(const QString& newOpid) {
main main
); );
watchingOps.remove(id); watchingOps.remove(id);
txTimer->start(Utils::updateSpeed);
main->ui->statusBar->showMessage(" Tx " % id % " failed", 15 * 1000); main->ui->statusBar->showMessage(" Tx " % id % " failed", 15 * 1000);
main->loadingLabel->setVisible(false); main->loadingLabel->setVisible(false);
msg.exec(); msg.exec();
} else if (status == "executing") { }
// If the operation is executing, then watch every second. }
txTimer->start(Utils::quickUpdateSpeed);
} if (watchingOps.isEmpty()) {
txTimer->start(Utils::updateSpeed);
} else {
txTimer->start(Utils::quickUpdateSpeed);
} }
} }
@ -646,6 +687,7 @@ void RPC::refreshTxStatus(const QString& newOpid) {
}); });
} }
// Get the ZEC->USD price from coinmarketcap using their API
void RPC::refreshZECPrice() { void RPC::refreshZECPrice() {
QUrl cmcURL("https://api.coinmarketcap.com/v1/ticker/"); QUrl cmcURL("https://api.coinmarketcap.com/v1/ticker/");

16
src/rpc.h

@ -11,6 +11,16 @@
using json = nlohmann::json; using json = nlohmann::json;
struct TransactionItem {
QString type;
unsigned long datetime;
QString address;
QString txid;
double amount;
unsigned long confirmations;
};
class RPC class RPC
{ {
public: public:
@ -18,11 +28,12 @@ public:
~RPC(); ~RPC();
void refresh(); // Refresh all transactions void refresh(); // Refresh all transactions
void refreshTxStatus(const QString& newOpid = QString()); // Refresh the status of all pending txs.
void refreshAddresses(); // Refresh wallet Z-addrs void refreshAddresses(); // Refresh wallet Z-addrs
void refreshZECPrice(); void refreshZECPrice();
void sendZTransaction (json params, const std::function<void(json)>& cb); void sendZTransaction (json params, const std::function<void(json)>& cb);
void watchTxStatus();
void addNewTxToWatch(Tx tx, const QString& newOpid);
BalancesTableModel* getBalancesModel() { return balancesTableModel; } BalancesTableModel* getBalancesModel() { return balancesTableModel; }
const QList<QString>* getAllZAddresses() { return zaddresses; } const QList<QString>* getAllZAddresses() { return zaddresses; }
@ -40,6 +51,7 @@ private:
void refreshBalances(); void refreshBalances();
void refreshTransactions(); void refreshTransactions();
void refreshZSentTransactions();
bool processUnspent (const json& reply); bool processUnspent (const json& reply);
void updateUI (bool anyUnconfirmed); void updateUI (bool anyUnconfirmed);
@ -69,7 +81,7 @@ private:
QMap<QString, double>* allBalances = nullptr; QMap<QString, double>* allBalances = nullptr;
QList<QString>* zaddresses = nullptr; QList<QString>* zaddresses = nullptr;
QSet<QString> watchingOps; QMap<QString, Tx> watchingOps;
TxTableModel* transactionsTableModel = nullptr; TxTableModel* transactionsTableModel = nullptr;
BalancesTableModel* balancesTableModel = nullptr; BalancesTableModel* balancesTableModel = nullptr;

7
src/sendtab.cpp

@ -319,6 +319,7 @@ Tx MainWindow::createTxFromSendPage() {
tx.toAddrs.push_back( ToFields{addr, amt, memo, memo.toUtf8().toHex()} ); tx.toAddrs.push_back( ToFields{addr, amt, memo, memo.toUtf8().toHex()} );
} }
tx.fee = Utils::getMinerFee();
return tx; return tx;
} }
@ -416,7 +417,7 @@ bool MainWindow::confirmTx(Tx tx, ToFields devFee) {
minerFee->setObjectName(QStringLiteral("minerFee")); minerFee->setObjectName(QStringLiteral("minerFee"));
minerFee->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); minerFee->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
confirm.gridLayout->addWidget(minerFee, i, 1, 1, 1); confirm.gridLayout->addWidget(minerFee, i, 1, 1, 1);
minerFee->setText(Settings::getInstance()->getZECDisplayFormat(Utils::getMinerFee())); minerFee->setText(Settings::getInstance()->getZECDisplayFormat(tx.fee));
auto minerFeeUSD = new QLabel(confirm.sendToAddrs); auto minerFeeUSD = new QLabel(confirm.sendToAddrs);
QSizePolicy sizePolicy1(QSizePolicy::Minimum, QSizePolicy::Preferred); QSizePolicy sizePolicy1(QSizePolicy::Minimum, QSizePolicy::Preferred);
@ -424,7 +425,7 @@ bool MainWindow::confirmTx(Tx tx, ToFields devFee) {
minerFeeUSD->setObjectName(QStringLiteral("minerFeeUSD")); minerFeeUSD->setObjectName(QStringLiteral("minerFeeUSD"));
minerFeeUSD->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); minerFeeUSD->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
confirm.gridLayout->addWidget(minerFeeUSD, i, 2, 1, 1); confirm.gridLayout->addWidget(minerFeeUSD, i, 2, 1, 1);
minerFeeUSD->setText(Settings::getInstance()->getUSDFormat(Utils::getMinerFee())); minerFeeUSD->setText(Settings::getInstance()->getUSDFormat(tx.fee));
if (!devFee.addr.isEmpty()) { if (!devFee.addr.isEmpty()) {
auto labelDevFee = new QLabel(confirm.sendToAddrs); auto labelDevFee = new QLabel(confirm.sendToAddrs);
@ -517,7 +518,7 @@ void MainWindow::sendButton() {
ui->statusBar->showMessage("Computing Tx: " % opid); ui->statusBar->showMessage("Computing Tx: " % opid);
// And then start monitoring the transaction // And then start monitoring the transaction
rpc->refreshTxStatus(opid); rpc->addNewTxToWatch(tx, opid);
}); });
} }
} }

86
src/senttxstore.cpp

@ -0,0 +1,86 @@
#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);
}
}
QList<TransactionItem> SentTxStore::readSentTxFile() {
QFile data(writeableFile());
if (!data.exists()) {
return QList<TransactionItem>();
}
QJsonDocument jsonDoc;
data.open(QFile::ReadOnly);
jsonDoc = QJsonDocument().fromJson(data.readAll());
data.close();
QList<TransactionItem> items;
for (auto i : jsonDoc.array()) {
auto sentTx = i.toObject();
TransactionItem t{"sent", (unsigned long)sentTx["datetime"].toInt(),
sentTx["address"].toString(),
sentTx["txid"].toString(), sentTx["amount"].toDouble(), 0};
items.push_back(t);
}
return items;
}
void SentTxStore::addToSentTx(Tx tx, QString txid) {
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;
}
auto list = jsonDoc.array();
QJsonObject txItem;
txItem["type"] = "sent";
txItem["datetime"] = QDateTime().currentSecsSinceEpoch();
txItem["address"] = QString(); // The sent address is blank, to be consistent with t-Addr sent behaviour
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();
}

18
src/senttxstore.h

@ -0,0 +1,18 @@
#ifndef SENTTXSTORE_H
#define SENTTXSTORE_H
#include "precompiled.h"
#include "mainwindow.h"
#include "rpc.h"
class SentTxStore {
public:
static QList<TransactionItem> readSentTxFile();
static void addToSentTx(Tx tx, QString txid);
private:
static QString writeableFile();
};
#endif // SENTTXSTORE_H

15
src/transactionitem.h

@ -1,15 +0,0 @@
#ifndef TRANSACTIONITEM_H
#define TRANSACTIONITEM_H
#include "precompiled.h"
struct TransactionItem {
QString type;
unsigned long datetime;
QString address;
QString txid;
double amount;
unsigned long confirmations;
};
#endif // TRANSACTIONITEM_H

62
src/txtablemodel.cpp

@ -1,6 +1,7 @@
#include "txtablemodel.h" #include "txtablemodel.h"
#include "settings.h" #include "settings.h"
#include "utils.h" #include "utils.h"
#include "rpc.h"
TxTableModel::TxTableModel(QObject *parent) TxTableModel::TxTableModel(QObject *parent)
: QAbstractTableModel(parent) { : QAbstractTableModel(parent) {
@ -11,33 +12,46 @@ TxTableModel::~TxTableModel() {
delete modeldata; delete modeldata;
} }
void TxTableModel::prepNewData(int expect) { void TxTableModel::addZSentData(const QList<TransactionItem>& data) {
newmodeldata = new QList<TransactionItem>(); delete zsTrans;
expectedData = expect; zsTrans = new QList<TransactionItem>();
std::copy(data.begin(), data.end(), std::back_inserter(*zsTrans));
updateAllData();
} }
void TxTableModel::addNewData(const QList<TransactionItem>& data) { void TxTableModel::addZRecvData(const QList<TransactionItem>& data) {
// Make sure we're expecting some data. delete zrTrans;
Q_ASSERT(expectedData > 0); zrTrans = new QList<TransactionItem>();
std::copy(data.begin(), data.end(), std::back_inserter(*zrTrans));
// Add all updateAllData();
std::copy(data.begin(), data.end(), std::back_inserter(*newmodeldata)); }
expectedData--;
if (expectedData == 0) {
delete modeldata;
modeldata = newmodeldata; void TxTableModel::addTData(const QList<TransactionItem>& data) {
newmodeldata = nullptr; delete tTrans;
tTrans = new QList<TransactionItem>();
std::copy(data.begin(), data.end(), std::back_inserter(*tTrans));
// Sort by reverse time updateAllData();
std::sort(modeldata->begin(), modeldata->end(), [=] (auto a, auto b) { }
return a.datetime > b.datetime; // reverse sort
});
dataChanged(index(0, 0), index(modeldata->size()-1, columnCount(index(0,0))-1)); void TxTableModel::updateAllData() {
layoutChanged(); delete modeldata;
} modeldata = new QList<TransactionItem>();
if (tTrans != nullptr) std::copy( tTrans->begin(), tTrans->end(), std::back_inserter(*modeldata));
if (zsTrans != nullptr) std::copy(zsTrans->begin(), zsTrans->end(), std::back_inserter(*modeldata));
if (zrTrans != nullptr) std::copy(zrTrans->begin(), zrTrans->end(), std::back_inserter(*modeldata));
// Sort by reverse time
std::sort(modeldata->begin(), modeldata->end(), [=] (auto a, auto b) {
return a.datetime > b.datetime; // reverse sort
});
dataChanged(index(0, 0), index(modeldata->size()-1, columnCount(index(0,0))-1));
layoutChanged();
} }
int TxTableModel::rowCount(const QModelIndex&) const int TxTableModel::rowCount(const QModelIndex&) const
@ -73,7 +87,13 @@ void TxTableModel::addNewData(const QList<TransactionItem>& data) {
if (role == Qt::DisplayRole || role == Qt::ToolTipRole) { if (role == Qt::DisplayRole || role == Qt::ToolTipRole) {
switch (index.column()) { switch (index.column()) {
case 0: return modeldata->at(index.row()).type; case 0: return modeldata->at(index.row()).type;
case 1: return modeldata->at(index.row()).address; case 1: {
auto addr = modeldata->at(index.row()).address;
if (addr.trimmed().isEmpty())
return "(Shielded)";
else
return addr;
}
case 2: return QDateTime::fromSecsSinceEpoch(modeldata->at(index.row()).datetime).toLocalTime().toString(); case 2: return QDateTime::fromSecsSinceEpoch(modeldata->at(index.row()).datetime).toLocalTime().toString();
case 3: { case 3: {
if (role == Qt::DisplayRole) if (role == Qt::DisplayRole)

18
src/txtablemodel.h

@ -1,17 +1,19 @@
#ifndef STRINGSTABLEMODEL_H #ifndef STRINGSTABLEMODEL_H
#define STRINGSTABLEMODEL_H #define STRINGSTABLEMODEL_H
#include "transactionitem.h"
#include "precompiled.h" #include "precompiled.h"
struct TransactionItem;
class TxTableModel: public QAbstractTableModel class TxTableModel: public QAbstractTableModel
{ {
public: public:
TxTableModel(QObject* parent); TxTableModel(QObject* parent);
~TxTableModel(); ~TxTableModel();
void prepNewData (int expectedData); void addTData (const QList<TransactionItem>& data);
void addNewData (const QList<TransactionItem>& data); void addZSentData(const QList<TransactionItem>& data);
void addZRecvData(const QList<TransactionItem>& data);
QString getTxId(int row); QString getTxId(int row);
@ -21,9 +23,13 @@ public:
QVariant headerData(int section, Qt::Orientation orientation, int role) const; QVariant headerData(int section, Qt::Orientation orientation, int role) const;
private: private:
QList<TransactionItem>* modeldata = nullptr; void updateAllData();
QList<TransactionItem>* newmodeldata = nullptr;
int expectedData; QList<TransactionItem>* tTrans = nullptr;
QList<TransactionItem>* zrTrans = nullptr; // Z received
QList<TransactionItem>* zsTrans = nullptr; // Z sent
QList<TransactionItem>* modeldata = nullptr;
QList<QString> headers; QList<QString> headers;
}; };

5
src/utils.h

@ -18,8 +18,9 @@ public:
static double getDevFee(); static double getDevFee();
static double getTotalFee(); static double getTotalFee();
static const int updateSpeed = 20 * 1000; // 20 sec static const int updateSpeed = 20 * 1000; // 20 sec
static const int quickUpdateSpeed = 5 * 1000; // 5 sec static const int quickUpdateSpeed = 5 * 1000; // 5 sec
static const int priceRefreshSpeed = 60 * 60 * 1000; // 1 hr
private: private:
Utils() = delete; Utils() = delete;
}; };

3
zcash-qt-wallet.pro

@ -48,6 +48,7 @@ SOURCES += \
src/3rdparty/qrcode/QrSegment.cpp \ src/3rdparty/qrcode/QrSegment.cpp \
src/settings.cpp \ src/settings.cpp \
src/sendtab.cpp \ src/sendtab.cpp \
src/senttxstore.cpp \
src/txtablemodel.cpp \ src/txtablemodel.cpp \
src/utils.cpp src/utils.cpp
@ -63,7 +64,7 @@ HEADERS += \
src/3rdparty/json/json.hpp \ src/3rdparty/json/json.hpp \
src/settings.h \ src/settings.h \
src/txtablemodel.h \ src/txtablemodel.h \
src/transactionitem.h \ src/senttxstore.h \
src/utils.h src/utils.h
FORMS += \ FORMS += \

Loading…
Cancel
Save