Browse Source

Add sent and received shielded Txs in the transactions tab.

recurring
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 {
QString fromAddr;
QList<ToFields> toAddrs;
double fee;
};
namespace Ui {

2
src/precompiled.h

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

86
src/rpc.cpp

@ -1,7 +1,7 @@
#include "rpc.h"
#include "utils.h"
#include "transactionitem.h"
#include "settings.h"
#include "senttxstore.h"
using json = nlohmann::json;
@ -35,7 +35,7 @@ RPC::RPC(QNetworkAccessManager* client, MainWindow* main) {
// Set up the timer to watch for tx status
txTimer = new QTimer(main);
QObject::connect(txTimer, &QTimer::timeout, [=]() {
refreshTxStatus();
watchTxStatus();
});
// Start at every 10s. When an operation is pending, this will change to every second
txTimer->start(Utils::updateSpeed);
@ -45,7 +45,7 @@ RPC::RPC(QNetworkAccessManager* client, MainWindow* main) {
QObject::connect(priceTimer, &QTimer::timeout, [=]() {
refreshZECPrice();
});
priceTimer->start(60 * 60 * 1000); // Every hour
priceTimer->start(Utils::priceRefreshSpeed); // Every hour
}
RPC::~RPC() {
@ -389,7 +389,7 @@ void RPC::getReceivedZTrans(QList<QString> zaddrs) {
}
}
transactionsTableModel->addNewData(txdata);
transactionsTableModel->addZRecvData(txdata);
// Cleanup both responses;
delete zaddrTxids;
@ -417,7 +417,7 @@ void RPC::getInfoThenRefresh() {
doRPC(payload, [=] (const json& reply) {
// Testnet?
if (reply.find("testnet") != reply.end()) {
if (!reply["testnet"].is_null()) {
Settings::getInstance()->setTestnet(reply["testnet"].get<json::boolean_t>());
};
@ -425,13 +425,11 @@ void RPC::getInfoThenRefresh() {
QIcon i(":/icons/res/connected.png");
main->statusIcon->setPixmap(i.pixmap(16, 16));
// Expect 2 data additions, then automatically refresh the table
transactionsTableModel->prepNewData(2);
// Refresh everything.
refreshBalances();
refreshAddresses();
refreshTransactions();
refreshZSentTransactions();
// Call to see if the blockchain is syncing.
json payload = {
@ -559,14 +557,14 @@ void RPC::refreshTransactions() {
for (auto& it : reply.get<json::array_t>()) {
double fee = 0;
if (it.find("fee") != it.end()) {
if (!it["fee"].is_null()) {
fee = it["fee"].get<json::number_float_t>();
}
TransactionItem tx{
QString::fromStdString(it["category"]),
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"]),
it["amount"].get<json::number_float_t>() + fee,
it["confirmations"].get<json::number_unsigned_t>()
@ -576,15 +574,55 @@ void RPC::refreshTransactions() {
}
// Update model data, which updates the table view
transactionsTableModel->addNewData(txdata);
transactionsTableModel->addTData(txdata);
});
}
void RPC::refreshTxStatus(const QString& newOpid) {
if (!newOpid.isEmpty()) {
watchingOps.insert(newOpid);
// Read sent Z transactions from the file.
void RPC::refreshZSentTransactions() {
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
json payload = {
{"jsonrpc", "1.0"},
@ -602,12 +640,13 @@ void RPC::refreshTxStatus(const QString& newOpid) {
QString status = QString::fromStdString(it["status"]);
if (status == "success") {
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->loadingLabel->setVisible(false);
watchingOps.remove(id);
txTimer->start(Utils::updateSpeed);
// Refresh balances to show unconfirmed balances
refresh();
@ -622,17 +661,19 @@ void RPC::refreshTxStatus(const QString& newOpid) {
main
);
watchingOps.remove(id);
txTimer->start(Utils::updateSpeed);
watchingOps.remove(id);
main->ui->statusBar->showMessage(" Tx " % id % " failed", 15 * 1000);
main->loadingLabel->setVisible(false);
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() {
QUrl cmcURL("https://api.coinmarketcap.com/v1/ticker/");

16
src/rpc.h

@ -11,6 +11,16 @@
using json = nlohmann::json;
struct TransactionItem {
QString type;
unsigned long datetime;
QString address;
QString txid;
double amount;
unsigned long confirmations;
};
class RPC
{
public:
@ -18,11 +28,12 @@ public:
~RPC();
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 refreshZECPrice();
void sendZTransaction (json params, const std::function<void(json)>& cb);
void watchTxStatus();
void addNewTxToWatch(Tx tx, const QString& newOpid);
BalancesTableModel* getBalancesModel() { return balancesTableModel; }
const QList<QString>* getAllZAddresses() { return zaddresses; }
@ -40,6 +51,7 @@ private:
void refreshBalances();
void refreshTransactions();
void refreshZSentTransactions();
bool processUnspent (const json& reply);
void updateUI (bool anyUnconfirmed);
@ -69,7 +81,7 @@ private:
QMap<QString, double>* allBalances = nullptr;
QList<QString>* zaddresses = nullptr;
QSet<QString> watchingOps;
QMap<QString, Tx> watchingOps;
TxTableModel* transactionsTableModel = 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.fee = Utils::getMinerFee();
return tx;
}
@ -416,7 +417,7 @@ bool MainWindow::confirmTx(Tx tx, ToFields devFee) {
minerFee->setObjectName(QStringLiteral("minerFee"));
minerFee->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
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);
QSizePolicy sizePolicy1(QSizePolicy::Minimum, QSizePolicy::Preferred);
@ -424,7 +425,7 @@ bool MainWindow::confirmTx(Tx tx, ToFields devFee) {
minerFeeUSD->setObjectName(QStringLiteral("minerFeeUSD"));
minerFeeUSD->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
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()) {
auto labelDevFee = new QLabel(confirm.sendToAddrs);
@ -517,7 +518,7 @@ void MainWindow::sendButton() {
ui->statusBar->showMessage("Computing Tx: " % opid);
// 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 "settings.h"
#include "utils.h"
#include "rpc.h"
TxTableModel::TxTableModel(QObject *parent)
: QAbstractTableModel(parent) {
@ -11,33 +12,46 @@ TxTableModel::~TxTableModel() {
delete modeldata;
}
void TxTableModel::prepNewData(int expect) {
newmodeldata = new QList<TransactionItem>();
expectedData = expect;
void TxTableModel::addZSentData(const QList<TransactionItem>& data) {
delete zsTrans;
zsTrans = new QList<TransactionItem>();
std::copy(data.begin(), data.end(), std::back_inserter(*zsTrans));
updateAllData();
}
void TxTableModel::addNewData(const QList<TransactionItem>& data) {
// Make sure we're expecting some data.
Q_ASSERT(expectedData > 0);
void TxTableModel::addZRecvData(const QList<TransactionItem>& data) {
delete zrTrans;
zrTrans = new QList<TransactionItem>();
std::copy(data.begin(), data.end(), std::back_inserter(*zrTrans));
// Add all
std::copy(data.begin(), data.end(), std::back_inserter(*newmodeldata));
expectedData--;
updateAllData();
}
if (expectedData == 0) {
delete modeldata;
modeldata = newmodeldata;
newmodeldata = nullptr;
void TxTableModel::addTData(const QList<TransactionItem>& data) {
delete tTrans;
tTrans = new QList<TransactionItem>();
std::copy(data.begin(), data.end(), std::back_inserter(*tTrans));
// Sort by reverse time
std::sort(modeldata->begin(), modeldata->end(), [=] (auto a, auto b) {
return a.datetime > b.datetime; // reverse sort
});
updateAllData();
}
dataChanged(index(0, 0), index(modeldata->size()-1, columnCount(index(0,0))-1));
layoutChanged();
}
void TxTableModel::updateAllData() {
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
@ -73,7 +87,13 @@ void TxTableModel::addNewData(const QList<TransactionItem>& data) {
if (role == Qt::DisplayRole || role == Qt::ToolTipRole) {
switch (index.column()) {
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 3: {
if (role == Qt::DisplayRole)

18
src/txtablemodel.h

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

5
src/utils.h

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

3
zcash-qt-wallet.pro

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

Loading…
Cancel
Save