Compile fixes
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -21,8 +21,8 @@ zec-qt-wallet.vcxproj*
|
|||||||
zecwallet.vcxproj*
|
zecwallet.vcxproj*
|
||||||
zec-qt-wallet.sln
|
zec-qt-wallet.sln
|
||||||
zec-qt-wallet.pro.user
|
zec-qt-wallet.pro.user
|
||||||
Makefile
|
/Makefile
|
||||||
Makefile.*
|
/Makefile.*
|
||||||
qrc_application.cpp
|
qrc_application.cpp
|
||||||
zec-qt-wallet_plugin_import.cpp
|
zec-qt-wallet_plugin_import.cpp
|
||||||
zecwallet_plugin_import.cpp
|
zecwallet_plugin_import.cpp
|
||||||
|
|||||||
24
lib/Makefile
Normal file
24
lib/Makefile
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
ifeq ($(shell uname),Darwin)
|
||||||
|
EXT := dylib
|
||||||
|
else
|
||||||
|
EXT := a
|
||||||
|
endif
|
||||||
|
|
||||||
|
all: release
|
||||||
|
|
||||||
|
winrelease: target/x86_64-pc-windows-gnu/release/zecwalletlite.lib
|
||||||
|
|
||||||
|
target/x86_64-pc-windows-gnu/release/zecwalletlite.lib: src/lib.rs Cargo.toml
|
||||||
|
cargo build --lib --release --target x86_64-pc-windows-gnu
|
||||||
|
|
||||||
|
release: target/release/zecwalletlite.$(EXT)
|
||||||
|
debug: target/debug/zecwalletlite.$(EXT)
|
||||||
|
|
||||||
|
target/release/zecwalletlite.$(EXT): src/lib.rs Cargo.toml
|
||||||
|
cargo build --lib --release
|
||||||
|
|
||||||
|
target/debug/zecwalletlite.$(EXT): src/lib.rs Cargo.toml
|
||||||
|
cargo build --lib
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf target
|
||||||
@@ -6,7 +6,7 @@ extern "C"{
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
extern char * litelib_initialze (bool dangerous, const char* server);
|
extern char * litelib_initialze (bool dangerous, const char* server);
|
||||||
extern char * litelib_execute (char* s);
|
extern char * litelib_execute (const char* s);
|
||||||
extern void litelib_rust_free_string (char* s);
|
extern void litelib_rust_free_string (char* s);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|||||||
@@ -40,85 +40,19 @@ void ConnectionLoader::doAutoConnect(bool tryEzcashdStart) {
|
|||||||
|
|
||||||
// Initialize the library
|
// Initialize the library
|
||||||
main->logger->write(QObject::tr("Attempting to initialize"));
|
main->logger->write(QObject::tr("Attempting to initialize"));
|
||||||
litelib_initialze(config->dangerous, config->server.toStdString());
|
litelib_initialze(config->dangerous, config->server.toStdString().c_str());
|
||||||
auto connection = makeConnection(config);
|
auto connection = makeConnection(config);
|
||||||
|
|
||||||
// After the lib is initialized, try to do get info
|
// After the lib is initialized, try to do get info
|
||||||
connection->doRPC("info", [=](auto reply) {
|
connection->doRPC("info", "", [=](auto reply) {
|
||||||
// If success, set the connection
|
// If success, set the connection
|
||||||
d->hide();
|
d->hide();
|
||||||
main->logger->write("Connection is online.");
|
main->logger->write("Connection is online.");
|
||||||
this->doRPCSetConnection(connection);
|
this->doRPCSetConnection(connection);
|
||||||
}, [=](auto err, auto errJson) {});
|
}, [=](auto err, auto errJson) {});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (config.get() != nullptr) {
|
|
||||||
auto connection = makeConnection(config);
|
|
||||||
|
|
||||||
refreshZcashdState(connection, [=] () {
|
|
||||||
// Refused connection. So try and start embedded zcashd
|
|
||||||
if (Settings::getInstance()->useEmbedded()) {
|
|
||||||
if (tryEzcashdStart) {
|
|
||||||
this->showInformation(QObject::tr("Starting embedded zcashd"));
|
|
||||||
if (this->startEmbeddedZcashd()) {
|
|
||||||
// Embedded zcashd started up. Wait a second and then refresh the connection
|
|
||||||
main->logger->write("Embedded zcashd started up, trying autoconnect in 1 sec");
|
|
||||||
QTimer::singleShot(1000, [=]() { doAutoConnect(); } );
|
|
||||||
} else {
|
|
||||||
if (config->zcashDaemon) {
|
|
||||||
// zcashd is configured to run as a daemon, so we must wait for a few seconds
|
|
||||||
// to let it start up.
|
|
||||||
main->logger->write("zcashd is daemon=1. Waiting for it to start up");
|
|
||||||
this->showInformation(QObject::tr("zcashd is set to run as daemon"), QObject::tr("Waiting for zcashd"));
|
|
||||||
QTimer::singleShot(5000, [=]() { doAutoConnect(/* don't attempt to start ezcashd */ false); });
|
|
||||||
} else {
|
|
||||||
// Something is wrong.
|
|
||||||
// We're going to attempt to connect to the one in the background one last time
|
|
||||||
// and see if that works, else throw an error
|
|
||||||
main->logger->write("Unknown problem while trying to start zcashd");
|
|
||||||
QTimer::singleShot(2000, [=]() { doAutoConnect(/* don't attempt to start ezcashd */ false); });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// We tried to start ezcashd previously, and it didn't work. So, show the error.
|
|
||||||
main->logger->write("Couldn't start embedded zcashd for unknown reason");
|
|
||||||
QString explanation;
|
|
||||||
if (config->zcashDaemon) {
|
|
||||||
explanation = QString() % QObject::tr("You have zcashd set to start as a daemon, which can cause problems "
|
|
||||||
"with ZecWallet\n\n."
|
|
||||||
"Please remove the following line from your zcash.conf and restart ZecWallet\n"
|
|
||||||
"daemon=1");
|
|
||||||
} else {
|
|
||||||
explanation = QString() % QObject::tr("Couldn't start the embedded zcashd.\n\n"
|
|
||||||
"Please try restarting.\n\nIf you previously started zcashd with custom arguments, you might need to reset zcash.conf.\n\n"
|
|
||||||
"If all else fails, please run zcashd manually.") %
|
|
||||||
(ezcashd ? QObject::tr("The process returned") + ":\n\n" % ezcashd->errorString() : QString(""));
|
|
||||||
}
|
|
||||||
|
|
||||||
this->showError(explanation);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// zcash.conf exists, there's no connection, and the user asked us not to start zcashd. Error!
|
|
||||||
main->logger->write("Not using embedded and couldn't connect to zcashd");
|
|
||||||
QString explanation = QString() % QObject::tr("Couldn't connect to zcashd configured in zcash.conf.\n\n"
|
|
||||||
"Not starting embedded zcashd because --no-embedded was passed");
|
|
||||||
this->showError(explanation);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (Settings::getInstance()->useEmbedded()) {
|
|
||||||
// zcash.conf was not found, so create one
|
|
||||||
createZcashConf();
|
|
||||||
} else {
|
|
||||||
// Fall back to manual connect
|
|
||||||
doManualConnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConnectionLoader::doRPCSetConnection(Connection* conn) {
|
void ConnectionLoader::doRPCSetConnection(Connection* conn) {
|
||||||
rpc->setEZcashd(ezcashd);
|
|
||||||
rpc->setConnection(conn);
|
rpc->setConnection(conn);
|
||||||
|
|
||||||
d->accept();
|
d->accept();
|
||||||
@@ -159,8 +93,10 @@ void ConnectionLoader::showError(QString explanation) {
|
|||||||
d->close();
|
d->close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void Executor::run() {
|
void Executor::run() {
|
||||||
const char* resp = litelib_execute(this->cmd.toStdString().c_str());
|
char* resp = litelib_execute(this->cmd.toStdString().c_str());
|
||||||
QString reply = QString::fromStdString(resp);
|
QString reply = QString::fromStdString(resp);
|
||||||
litelib_rust_free_string(resp);
|
litelib_rust_free_string(resp);
|
||||||
|
|
||||||
@@ -193,12 +129,13 @@ void Connection::doRPC(const QString cmd, const QString args, const std::functio
|
|||||||
auto runner = new Executor(cmd, args);
|
auto runner = new Executor(cmd, args);
|
||||||
QObject::connect(runner, &Executor::responseReady, [=] (json resp) {
|
QObject::connect(runner, &Executor::responseReady, [=] (json resp) {
|
||||||
cb(resp);
|
cb(resp);
|
||||||
})
|
});
|
||||||
|
|
||||||
QThreadPool::globalInstance()->start(runner);
|
QThreadPool::globalInstance()->start(runner);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Connection::doRPCWithDefaultErrorHandling(const json& payload, const std::function<void(json)>& cb) {
|
void Connection::doRPCWithDefaultErrorHandling(const QString cmd, const QString args, const std::function<void(json)>& cb) {
|
||||||
doRPC(payload, cb, [=] (auto reply, auto parsed) {
|
doRPC(cmd, args, cb, [=] (auto reply, auto parsed) {
|
||||||
if (!parsed.is_discarded() && !parsed["error"]["message"].is_null()) {
|
if (!parsed.is_discarded() && !parsed["error"]["message"].is_null()) {
|
||||||
this->showTxError(QString::fromStdString(parsed["error"]["message"]));
|
this->showTxError(QString::fromStdString(parsed["error"]["message"]));
|
||||||
} else {
|
} else {
|
||||||
@@ -207,8 +144,8 @@ void Connection::doRPCWithDefaultErrorHandling(const json& payload, const std::f
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Connection::doRPCIgnoreError(const json& payload, const std::function<void(json)>& cb) {
|
void Connection::doRPCIgnoreError(const QString cmd, const QString args, const std::function<void(json)>& cb) {
|
||||||
doRPC(payload, cb, [=] (auto, auto) {
|
doRPC(cmd, args, cb, [=] (auto, auto) {
|
||||||
// Ignored error handling
|
// Ignored error handling
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
107
src/connection.h
107
src/connection.h
@@ -12,6 +12,7 @@ class Controller;
|
|||||||
struct ConnectionConfig {
|
struct ConnectionConfig {
|
||||||
QString server;
|
QString server;
|
||||||
bool dangerous;
|
bool dangerous;
|
||||||
|
QString proxy;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Connection;
|
class Connection;
|
||||||
@@ -31,42 +32,22 @@ private:
|
|||||||
Connection* makeConnection(std::shared_ptr<ConnectionConfig> config);
|
Connection* makeConnection(std::shared_ptr<ConnectionConfig> config);
|
||||||
|
|
||||||
void doAutoConnect(bool tryEzcashdStart = true);
|
void doAutoConnect(bool tryEzcashdStart = true);
|
||||||
void doManualConnect();
|
|
||||||
|
|
||||||
void createZcashConf();
|
|
||||||
QString locateZcashConfFile();
|
|
||||||
QString zcashConfWritableLocation();
|
|
||||||
QString zcashParamsDir();
|
|
||||||
|
|
||||||
bool verifyParams();
|
|
||||||
void downloadParams(std::function<void(void)> cb);
|
|
||||||
void doNextDownload(std::function<void(void)> cb);
|
|
||||||
bool startEmbeddedZcashd();
|
|
||||||
|
|
||||||
void refreshZcashdState(Connection* connection, std::function<void(void)> refused);
|
|
||||||
|
|
||||||
void showError(QString explanation);
|
void showError(QString explanation);
|
||||||
void showInformation(QString info, QString detail = "");
|
void showInformation(QString info, QString detail = "");
|
||||||
|
|
||||||
void doRPCSetConnection(Connection* conn);
|
void doRPCSetConnection(Connection* conn);
|
||||||
|
|
||||||
QProcess* ezcashd = nullptr;
|
|
||||||
|
|
||||||
QDialog* d;
|
QDialog* d;
|
||||||
Ui_ConnectionDialog* connD;
|
Ui_ConnectionDialog* connD;
|
||||||
|
|
||||||
MainWindow* main;
|
MainWindow* main;
|
||||||
Controller* rpc;
|
Controller* rpc;
|
||||||
|
|
||||||
QNetworkReply* currentDownload = nullptr;
|
|
||||||
QFile* currentOutput = nullptr;
|
|
||||||
QQueue<QUrl>* downloadQueue = nullptr;
|
|
||||||
|
|
||||||
QNetworkAccessManager* client = nullptr;
|
|
||||||
QTime downloadTime;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class Executor : public QRunnable {
|
class Executor : public QObject, public QRunnable {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Executor(QString cmd, QString args) {
|
Executor(QString cmd, QString args) {
|
||||||
this->cmd = cmd;
|
this->cmd = cmd;
|
||||||
@@ -84,7 +65,7 @@ signals:
|
|||||||
private:
|
private:
|
||||||
QString cmd;
|
QString cmd;
|
||||||
QString args;
|
QString args;
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a connection to a zcashd. It may even start a new zcashd if needed.
|
* Represents a connection to a zcashd. It may even start a new zcashd if needed.
|
||||||
@@ -100,85 +81,13 @@ public:
|
|||||||
|
|
||||||
void shutdown();
|
void shutdown();
|
||||||
|
|
||||||
void doRPC(const json& payload, const std::function<void(json)>& cb,
|
void doRPC(const QString cmd, const QString args, const std::function<void(json)>& cb,
|
||||||
const std::function<void(QNetworkReply*, const json&)>& ne);
|
const std::function<void(QNetworkReply*, const json&)>& ne);
|
||||||
void doRPCWithDefaultErrorHandling(const json& payload, const std::function<void(json)>& cb);
|
void doRPCWithDefaultErrorHandling(const QString cmd, const QString args, const std::function<void(json)>& cb);
|
||||||
void doRPCIgnoreError(const json& payload, const std::function<void(json)>& cb) ;
|
void doRPCIgnoreError(const QString cmd, const QString args, const std::function<void(json)>& cb) ;
|
||||||
|
|
||||||
void showTxError(const QString& error);
|
void showTxError(const QString& error);
|
||||||
|
|
||||||
// Batch method. Note: Because of the template, it has to be in the header file.
|
|
||||||
template<class T>
|
|
||||||
void doBatchRPC(const QList<T>& payloads,
|
|
||||||
std::function<json(T)> payloadGenerator,
|
|
||||||
std::function<void(QMap<T, json>*)> cb) {
|
|
||||||
auto responses = new QMap<T, json>(); // zAddr -> list of responses for each call.
|
|
||||||
int totalSize = payloads.size();
|
|
||||||
if (totalSize == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Keep track of all pending method calls, so as to prevent
|
|
||||||
// any overlapping calls
|
|
||||||
static QMap<QString, bool> inProgress;
|
|
||||||
|
|
||||||
QString method = QString::fromStdString(payloadGenerator(payloads[0])["method"]);
|
|
||||||
//if (inProgress.value(method, false)) {
|
|
||||||
// qDebug() << "In progress batch, skipping";
|
|
||||||
// return;
|
|
||||||
//}
|
|
||||||
|
|
||||||
for (auto item: payloads) {
|
|
||||||
json payload = payloadGenerator(item);
|
|
||||||
inProgress[method] = true;
|
|
||||||
|
|
||||||
QNetworkReply *reply = restclient->post(*request, QByteArray::fromStdString(payload.dump()));
|
|
||||||
|
|
||||||
QObject::connect(reply, &QNetworkReply::finished, [=] {
|
|
||||||
reply->deleteLater();
|
|
||||||
if (shutdownInProgress) {
|
|
||||||
// Ignoring callback because shutdown in progress
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto all = reply->readAll();
|
|
||||||
auto parsed = json::parse(all.toStdString(), nullptr, false);
|
|
||||||
|
|
||||||
if (reply->error() != QNetworkReply::NoError) {
|
|
||||||
qDebug() << QString::fromStdString(parsed.dump());
|
|
||||||
qDebug() << reply->errorString();
|
|
||||||
|
|
||||||
(*responses)[item] = json::object(); // Empty object
|
|
||||||
} else {
|
|
||||||
if (parsed.is_discarded()) {
|
|
||||||
(*responses)[item] = json::object(); // Empty object
|
|
||||||
} else {
|
|
||||||
(*responses)[item] = parsed["result"];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
auto waitTimer = new QTimer(main);
|
|
||||||
QObject::connect(waitTimer, &QTimer::timeout, [=]() {
|
|
||||||
if (shutdownInProgress) {
|
|
||||||
waitTimer->stop();
|
|
||||||
waitTimer->deleteLater();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If all responses have arrived, return
|
|
||||||
if (responses->size() == totalSize) {
|
|
||||||
waitTimer->stop();
|
|
||||||
|
|
||||||
cb(responses);
|
|
||||||
inProgress[method] = false;
|
|
||||||
|
|
||||||
waitTimer->deleteLater();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
waitTimer->start(100);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool shutdownInProgress = false;
|
bool shutdownInProgress = false;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ Controller::Controller(MainWindow* main) {
|
|||||||
model = new DataModel();
|
model = new DataModel();
|
||||||
|
|
||||||
// Crate the ZcashdRPC
|
// Crate the ZcashdRPC
|
||||||
zrpc = new ZcashdRPC();
|
zrpc = new LiteInterface();
|
||||||
|
|
||||||
// Initialize the migration status to unavailable.
|
// Initialize the migration status to unavailable.
|
||||||
this->migrationStatus.available = false;
|
this->migrationStatus.available = false;
|
||||||
@@ -283,14 +283,6 @@ void Controller::getInfoThenRefresh(bool force) {
|
|||||||
ui->blockheight->setText(txt);
|
ui->blockheight->setText(txt);
|
||||||
ui->heightLabel->setText(QObject::tr("Downloading blocks"));
|
ui->heightLabel->setText(QObject::tr("Downloading blocks"));
|
||||||
} else {
|
} else {
|
||||||
// If syncing is finished, we may have to remove the ibdskiptxverification
|
|
||||||
// flag from zcash.conf
|
|
||||||
if (getConnection() != nullptr && getConnection()->config->skiptxverification) {
|
|
||||||
getConnection()->config->skiptxverification = false;
|
|
||||||
Settings::removeFromZcashConf(Settings::getInstance()->getZcashdConfLocation(),
|
|
||||||
"ibdskiptxverification");
|
|
||||||
}
|
|
||||||
|
|
||||||
ui->blockheight->setText(QString::number(blockNumber));
|
ui->blockheight->setText(QString::number(blockNumber));
|
||||||
ui->heightLabel->setText(QObject::tr("Block height"));
|
ui->heightLabel->setText(QObject::tr("Block height"));
|
||||||
}
|
}
|
||||||
@@ -649,10 +641,12 @@ void Controller::checkForUpdate(bool silent) {
|
|||||||
QNetworkRequest req;
|
QNetworkRequest req;
|
||||||
req.setUrl(cmcURL);
|
req.setUrl(cmcURL);
|
||||||
|
|
||||||
QNetworkReply *reply = getConnection()->restclient->get(req);
|
QNetworkAccessManager *manager = new QNetworkAccessManager(this->main);
|
||||||
|
QNetworkReply *reply = manager->get(req);
|
||||||
|
|
||||||
QObject::connect(reply, &QNetworkReply::finished, [=] {
|
QObject::connect(reply, &QNetworkReply::finished, [=] {
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
|
manager->deleteLater();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (reply->error() == QNetworkReply::NoError) {
|
if (reply->error() == QNetworkReply::NoError) {
|
||||||
@@ -721,10 +715,12 @@ void Controller::refreshZECPrice() {
|
|||||||
QNetworkRequest req;
|
QNetworkRequest req;
|
||||||
req.setUrl(cmcURL);
|
req.setUrl(cmcURL);
|
||||||
|
|
||||||
QNetworkReply *reply = getConnection()->restclient->get(req);
|
QNetworkAccessManager *manager = new QNetworkAccessManager(this->main);
|
||||||
|
QNetworkReply *reply = manager->get(req);
|
||||||
|
|
||||||
QObject::connect(reply, &QNetworkReply::finished, [=] {
|
QObject::connect(reply, &QNetworkReply::finished, [=] {
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
|
manager->deleteLater();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (reply->error() != QNetworkReply::NoError) {
|
if (reply->error() != QNetworkReply::NoError) {
|
||||||
@@ -767,59 +763,114 @@ void Controller::refreshZECPrice() {
|
|||||||
|
|
||||||
void Controller::shutdownZcashd() {
|
void Controller::shutdownZcashd() {
|
||||||
// Shutdown embedded zcashd if it was started
|
// Shutdown embedded zcashd if it was started
|
||||||
if (ezcashd == nullptr || ezcashd->processId() == 0 || !zrpc->haveConnection()) {
|
if (ezcashd == nullptr || ezcashd->processId() == 0 || ~zrpc->haveConnection()) {
|
||||||
// No zcashd running internally, just return
|
// No zcashd running internally, just return
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
json payload = {
|
// json payload = {
|
||||||
{"jsonrpc", "1.0"},
|
// {"jsonrpc", "1.0"},
|
||||||
{"id", "someid"},
|
// {"id", "someid"},
|
||||||
{"method", "stop"}
|
// {"method", "stop"}
|
||||||
};
|
// };
|
||||||
|
|
||||||
getConnection()->doRPCWithDefaultErrorHandling(payload, [=](auto) {});
|
// getConnection()->doRPCWithDefaultErrorHandling(payload, [=](auto) {});
|
||||||
getConnection()->shutdown();
|
// getConnection()->shutdown();
|
||||||
|
|
||||||
QDialog d(main);
|
// QDialog d(main);
|
||||||
Ui_ConnectionDialog connD;
|
// Ui_ConnectionDialog connD;
|
||||||
connD.setupUi(&d);
|
// connD.setupUi(&d);
|
||||||
connD.topIcon->setBasePixmap(QIcon(":/icons/res/icon.ico").pixmap(256, 256));
|
// connD.topIcon->setBasePixmap(QIcon(":/icons/res/icon.ico").pixmap(256, 256));
|
||||||
connD.status->setText(QObject::tr("Please wait for ZecWallet to exit"));
|
// connD.status->setText(QObject::tr("Please wait for ZecWallet to exit"));
|
||||||
connD.statusDetail->setText(QObject::tr("Waiting for zcashd to exit"));
|
// connD.statusDetail->setText(QObject::tr("Waiting for zcashd to exit"));
|
||||||
|
|
||||||
QTimer waiter(main);
|
// QTimer waiter(main);
|
||||||
|
|
||||||
// We capture by reference all the local variables because of the d.exec()
|
// // We capture by reference all the local variables because of the d.exec()
|
||||||
// below, which blocks this function until we exit.
|
// // below, which blocks this function until we exit.
|
||||||
int waitCount = 0;
|
// int waitCount = 0;
|
||||||
QObject::connect(&waiter, &QTimer::timeout, [&] () {
|
// QObject::connect(&waiter, &QTimer::timeout, [&] () {
|
||||||
waitCount++;
|
// waitCount++;
|
||||||
|
|
||||||
if ((ezcashd->atEnd() && ezcashd->processId() == 0) ||
|
// if ((ezcashd->atEnd() && ezcashd->processId() == 0) ||
|
||||||
waitCount > 30 ||
|
// waitCount > 30 ||
|
||||||
getConnection()->config->zcashDaemon) { // If zcashd is daemon, then we don't have to do anything else
|
// getConnection()->config->zcashDaemon) { // If zcashd is daemon, then we don't have to do anything else
|
||||||
qDebug() << "Ended";
|
// qDebug() << "Ended";
|
||||||
waiter.stop();
|
// waiter.stop();
|
||||||
QTimer::singleShot(1000, [&]() { d.accept(); });
|
// QTimer::singleShot(1000, [&]() { d.accept(); });
|
||||||
} else {
|
// } else {
|
||||||
qDebug() << "Not ended, continuing to wait...";
|
// qDebug() << "Not ended, continuing to wait...";
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
waiter.start(1000);
|
// waiter.start(1000);
|
||||||
|
|
||||||
// Wait for the zcash process to exit.
|
// // Wait for the zcash process to exit.
|
||||||
if (!Settings::getInstance()->isHeadless()) {
|
// if (!Settings::getInstance()->isHeadless()) {
|
||||||
d.exec();
|
// d.exec();
|
||||||
} else {
|
// } else {
|
||||||
while (waiter.isActive()) {
|
// while (waiter.isActive()) {
|
||||||
QCoreApplication::processEvents();
|
// QCoreApplication::processEvents();
|
||||||
|
|
||||||
QThread::sleep(1);
|
// QThread::sleep(1);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// // Fetch the Z-board topics list
|
||||||
|
// void Controller::getZboardTopics(std::function<void(QMap<QString, QString>)> cb) {
|
||||||
|
// if (!zrpc->haveConnection())
|
||||||
|
// return noConnection();
|
||||||
|
|
||||||
|
// QUrl cmcURL("http://z-board.net/listTopics");
|
||||||
|
|
||||||
|
// QNetworkRequest req;
|
||||||
|
// req.setUrl(cmcURL);
|
||||||
|
|
||||||
|
// QNetworkReply *reply = conn->restclient->get(req);
|
||||||
|
|
||||||
|
// QObject::connect(reply, &QNetworkReply::finished, [=] {
|
||||||
|
// reply->deleteLater();
|
||||||
|
|
||||||
|
// try {
|
||||||
|
// if (reply->error() != QNetworkReply::NoError) {
|
||||||
|
// auto parsed = json::parse(reply->readAll(), nullptr, false);
|
||||||
|
// if (!parsed.is_discarded() && !parsed["error"]["message"].is_null()) {
|
||||||
|
// qDebug() << QString::fromStdString(parsed["error"]["message"]);
|
||||||
|
// }
|
||||||
|
// else {
|
||||||
|
// qDebug() << reply->errorString();
|
||||||
|
// }
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// auto all = reply->readAll();
|
||||||
|
|
||||||
|
// auto parsed = json::parse(all, nullptr, false);
|
||||||
|
// if (parsed.is_discarded()) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// QMap<QString, QString> topics;
|
||||||
|
// for (const json& item : parsed["topics"].get<json::array_t>()) {
|
||||||
|
// if (item.find("addr") == item.end() || item.find("topicName") == item.end())
|
||||||
|
// return;
|
||||||
|
|
||||||
|
// QString addr = QString::fromStdString(item["addr"].get<json::string_t>());
|
||||||
|
// QString topic = QString::fromStdString(item["topicName"].get<json::string_t>());
|
||||||
|
|
||||||
|
// topics.insert(topic, addr);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// cb(topics);
|
||||||
|
// }
|
||||||
|
// catch (...) {
|
||||||
|
// // If anything at all goes wrong, just set the price to 0 and move on.
|
||||||
|
// qDebug() << QString("Caught something nasty");
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a Sapling address from the user's wallet
|
* Get a Sapling address from the user's wallet
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
#include "txtablemodel.h"
|
#include "txtablemodel.h"
|
||||||
#include "ui_mainwindow.h"
|
#include "ui_mainwindow.h"
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include "zcashdrpc.h"
|
#include "liteinterface.h"
|
||||||
#include "connection.h"
|
#include "connection.h"
|
||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
@@ -109,7 +109,7 @@ private:
|
|||||||
BalancesTableModel* balancesTableModel = nullptr;
|
BalancesTableModel* balancesTableModel = nullptr;
|
||||||
|
|
||||||
DataModel* model;
|
DataModel* model;
|
||||||
ZcashdRPC* zrpc;
|
LiteInterface* zrpc;
|
||||||
|
|
||||||
QTimer* timer;
|
QTimer* timer;
|
||||||
QTimer* txTimer;
|
QTimer* txTimer;
|
||||||
|
|||||||
545
src/liteinterface.cpp
Normal file
545
src/liteinterface.cpp
Normal file
@@ -0,0 +1,545 @@
|
|||||||
|
#include "liteinterface.h"
|
||||||
|
|
||||||
|
LiteInterface::LiteInterface() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
LiteInterface::~LiteInterface() {
|
||||||
|
delete conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiteInterface::setConnection(Connection* c) {
|
||||||
|
if (conn) {
|
||||||
|
delete conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
conn = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LiteInterface::haveConnection() {
|
||||||
|
return conn != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiteInterface::fetchTAddresses(const std::function<void(json)>& cb) {
|
||||||
|
if (conn == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// json payload = {
|
||||||
|
// {"jsonrpc", "1.0"},
|
||||||
|
// {"id", "someid"},
|
||||||
|
// {"method", "getaddressesbyaccount"},
|
||||||
|
// {"params", {""}}
|
||||||
|
// };
|
||||||
|
|
||||||
|
// conn->doRPCWithDefaultErrorHandling(payload, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiteInterface::fetchZAddresses(const std::function<void(json)>& cb) {
|
||||||
|
if (conn == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// json payload = {
|
||||||
|
// {"jsonrpc", "1.0"},
|
||||||
|
// {"id", "someid"},
|
||||||
|
// {"method", "z_listaddresses"},
|
||||||
|
// };
|
||||||
|
|
||||||
|
// conn->doRPCWithDefaultErrorHandling(payload, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiteInterface::fetchTransparentUnspent(const std::function<void(json)>& cb) {
|
||||||
|
if (conn == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// json payload = {
|
||||||
|
// {"jsonrpc", "1.0"},
|
||||||
|
// {"id", "someid"},
|
||||||
|
// {"method", "listunspent"},
|
||||||
|
// {"params", {0}} // Get UTXOs with 0 confirmations as well.
|
||||||
|
// };
|
||||||
|
|
||||||
|
// conn->doRPCWithDefaultErrorHandling(payload, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiteInterface::fetchZUnspent(const std::function<void(json)>& cb) {
|
||||||
|
if (conn == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// json payload = {
|
||||||
|
// {"jsonrpc", "1.0"},
|
||||||
|
// {"id", "someid"},
|
||||||
|
// {"method", "z_listunspent"},
|
||||||
|
// {"params", {0}} // Get UTXOs with 0 confirmations as well.
|
||||||
|
// };
|
||||||
|
|
||||||
|
// conn->doRPCWithDefaultErrorHandling(payload, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiteInterface::createNewZaddr(bool sapling, const std::function<void(json)>& cb) {
|
||||||
|
if (conn == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// json payload = {
|
||||||
|
// {"jsonrpc", "1.0"},
|
||||||
|
// {"id", "someid"},
|
||||||
|
// {"method", "z_getnewaddress"},
|
||||||
|
// {"params", { sapling ? "sapling" : "sprout" }},
|
||||||
|
// };
|
||||||
|
|
||||||
|
// conn->doRPCWithDefaultErrorHandling(payload, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiteInterface::createNewTaddr(const std::function<void(json)>& cb) {
|
||||||
|
if (conn == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// json payload = {
|
||||||
|
// {"jsonrpc", "1.0"},
|
||||||
|
// {"id", "someid"},
|
||||||
|
// {"method", "getnewaddress"},
|
||||||
|
// };
|
||||||
|
|
||||||
|
// conn->doRPCWithDefaultErrorHandling(payload, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiteInterface::fetchZPrivKey(QString addr, const std::function<void(json)>& cb) {
|
||||||
|
if (conn == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// json payload = {
|
||||||
|
// {"jsonrpc", "1.0"},
|
||||||
|
// {"id", "someid"},
|
||||||
|
// {"method", "z_exportkey"},
|
||||||
|
// {"params", { addr.toStdString() }},
|
||||||
|
// };
|
||||||
|
|
||||||
|
// conn->doRPCWithDefaultErrorHandling(payload, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiteInterface::fetchTPrivKey(QString addr, const std::function<void(json)>& cb) {
|
||||||
|
if (conn == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// json payload = {
|
||||||
|
// {"jsonrpc", "1.0"},
|
||||||
|
// {"id", "someid"},
|
||||||
|
// {"method", "dumpprivkey"},
|
||||||
|
// {"params", { addr.toStdString() }},
|
||||||
|
// };
|
||||||
|
|
||||||
|
// conn->doRPCWithDefaultErrorHandling(payload, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiteInterface::importZPrivKey(QString addr, bool rescan, const std::function<void(json)>& cb) {
|
||||||
|
if (conn == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// json payload = {
|
||||||
|
// {"jsonrpc", "1.0"},
|
||||||
|
// {"id", "someid"},
|
||||||
|
// {"method", "z_importkey"},
|
||||||
|
// {"params", { addr.toStdString(), (rescan? "yes" : "no") }},
|
||||||
|
// };
|
||||||
|
|
||||||
|
// conn->doRPCWithDefaultErrorHandling(payload, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void LiteInterface::importTPrivKey(QString addr, bool rescan, const std::function<void(json)>& cb) {
|
||||||
|
if (conn == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// json payload = {
|
||||||
|
// {"jsonrpc", "1.0"},
|
||||||
|
// {"id", "someid"},
|
||||||
|
// {"method", "importprivkey"},
|
||||||
|
// {"params", { addr.toStdString(), (rescan? "yes" : "no") }},
|
||||||
|
// };
|
||||||
|
|
||||||
|
// conn->doRPCWithDefaultErrorHandling(payload, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiteInterface::validateAddress(QString address, const std::function<void(json)>& cb) {
|
||||||
|
if (conn == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// QString method = Settings::isZAddress(address) ? "z_validateaddress" : "validateaddress";
|
||||||
|
|
||||||
|
// json payload = {
|
||||||
|
// {"jsonrpc", "1.0"},
|
||||||
|
// {"id", "someid"},
|
||||||
|
// {"method", method.toStdString() },
|
||||||
|
// {"params", { address.toStdString() } },
|
||||||
|
// };
|
||||||
|
|
||||||
|
// conn->doRPCWithDefaultErrorHandling(payload, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiteInterface::fetchBalance(const std::function<void(json)>& cb) {
|
||||||
|
if (conn == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// json payload = {
|
||||||
|
// {"jsonrpc", "1.0"},
|
||||||
|
// {"id", "someid"},
|
||||||
|
// {"method", "z_gettotalbalance"},
|
||||||
|
// {"params", {0}} // Get Unconfirmed balance as well.
|
||||||
|
// };
|
||||||
|
|
||||||
|
// conn->doRPCWithDefaultErrorHandling(payload, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiteInterface::fetchTransactions(const std::function<void(json)>& cb) {
|
||||||
|
if (conn == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// json payload = {
|
||||||
|
// {"jsonrpc", "1.0"},
|
||||||
|
// {"id", "someid"},
|
||||||
|
// {"method", "listtransactions"}
|
||||||
|
// };
|
||||||
|
|
||||||
|
// conn->doRPCWithDefaultErrorHandling(payload, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiteInterface::sendZTransaction(json params, const std::function<void(json)>& cb,
|
||||||
|
const std::function<void(QString)>& err) {
|
||||||
|
if (conn == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// json payload = {
|
||||||
|
// {"jsonrpc", "1.0"},
|
||||||
|
// {"id", "someid"},
|
||||||
|
// {"method", "z_sendmany"},
|
||||||
|
// {"params", params}
|
||||||
|
// };
|
||||||
|
|
||||||
|
// conn->doRPC(payload, cb, [=] (auto reply, auto parsed) {
|
||||||
|
// if (!parsed.is_discarded() && !parsed["error"]["message"].is_null()) {
|
||||||
|
// err(QString::fromStdString(parsed["error"]["message"]));
|
||||||
|
// } else {
|
||||||
|
// err(reply->errorString());
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiteInterface::fetchInfo(const std::function<void(json)>& cb,
|
||||||
|
const std::function<void(QNetworkReply*, const json&)>& err) {
|
||||||
|
if (conn == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// json payload = {
|
||||||
|
// {"jsonrpc", "1.0"},
|
||||||
|
// {"id", "someid"},
|
||||||
|
// {"method", "getinfo"}
|
||||||
|
// };
|
||||||
|
|
||||||
|
// conn->doRPC(payload, cb, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiteInterface::fetchBlockchainInfo(const std::function<void(json)>& cb) {
|
||||||
|
if (conn == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// json payload = {
|
||||||
|
// {"jsonrpc", "1.0"},
|
||||||
|
// {"id", "someid"},
|
||||||
|
// {"method", "getblockchaininfo"}
|
||||||
|
// };
|
||||||
|
|
||||||
|
// conn->doRPCIgnoreError(payload, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiteInterface::fetchNetSolOps(const std::function<void(qint64)> cb) {
|
||||||
|
if (conn == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// json payload = {
|
||||||
|
// {"jsonrpc", "1.0"},
|
||||||
|
// {"id", "someid"},
|
||||||
|
// {"method", "getnetworksolps"}
|
||||||
|
// };
|
||||||
|
|
||||||
|
// conn->doRPCIgnoreError(payload, [=](const json& reply) {
|
||||||
|
// qint64 solrate = reply.get<json::number_unsigned_t>();
|
||||||
|
// cb(solrate);
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiteInterface::fetchMigrationStatus(const std::function<void(json)>& cb) {
|
||||||
|
if (conn == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// json payload = {
|
||||||
|
// {"jsonrpc", "1.0"},
|
||||||
|
// {"id", "someid"},
|
||||||
|
// {"method", "z_getmigrationstatus"},
|
||||||
|
// };
|
||||||
|
|
||||||
|
// conn->doRPCWithDefaultErrorHandling(payload, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void LiteInterface::setMigrationStatus(bool enabled) {
|
||||||
|
if (conn == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// json payload = {
|
||||||
|
// {"jsonrpc", "1.0"},
|
||||||
|
// {"id", "someid"},
|
||||||
|
// {"method", "z_setmigration"},
|
||||||
|
// {"params", {enabled}}
|
||||||
|
// };
|
||||||
|
|
||||||
|
// conn->doRPCWithDefaultErrorHandling(payload, [=](json) {
|
||||||
|
// // Ignore return value.
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to get all the private keys for both z and t addresses. It will make 2 batch calls,
|
||||||
|
* combine the result, and call the callback with a single list containing both the t-addr and z-addr
|
||||||
|
* private keys
|
||||||
|
*/
|
||||||
|
void LiteInterface::fetchAllPrivKeys(const std::function<void(QList<QPair<QString, QString>>)> cb) {
|
||||||
|
if (conn == nullptr) {
|
||||||
|
// No connection, just return
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// // A special function that will call the callback when two lists have been added
|
||||||
|
// auto holder = new QPair<int, QList<QPair<QString, QString>>>();
|
||||||
|
// holder->first = 0; // This is the number of times the callback has been called, initialized to 0
|
||||||
|
// auto fnCombineTwoLists = [=] (QList<QPair<QString, QString>> list) {
|
||||||
|
// // Increment the callback counter
|
||||||
|
// holder->first++;
|
||||||
|
|
||||||
|
// // Add all
|
||||||
|
// std::copy(list.begin(), list.end(), std::back_inserter(holder->second));
|
||||||
|
|
||||||
|
// // And if the caller has been called twice, do the parent callback with the
|
||||||
|
// // collected list
|
||||||
|
// if (holder->first == 2) {
|
||||||
|
// // Sort so z addresses are on top
|
||||||
|
// std::sort(holder->second.begin(), holder->second.end(),
|
||||||
|
// [=] (auto a, auto b) { return a.first > b.first; });
|
||||||
|
|
||||||
|
// cb(holder->second);
|
||||||
|
// delete holder;
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
// // A utility fn to do the batch calling
|
||||||
|
// auto fnDoBatchGetPrivKeys = [=](json getAddressPayload, std::string privKeyDumpMethodName) {
|
||||||
|
// conn->doRPCWithDefaultErrorHandling(getAddressPayload, [=] (json resp) {
|
||||||
|
// QList<QString> addrs;
|
||||||
|
// for (auto addr : resp.get<json::array_t>()) {
|
||||||
|
// addrs.push_back(QString::fromStdString(addr.get<json::string_t>()));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Then, do a batch request to get all the private keys
|
||||||
|
// conn->doBatchRPC<QString>(
|
||||||
|
// addrs,
|
||||||
|
// [=] (auto addr) {
|
||||||
|
// json payload = {
|
||||||
|
// {"jsonrpc", "1.0"},
|
||||||
|
// {"id", "someid"},
|
||||||
|
// {"method", privKeyDumpMethodName},
|
||||||
|
// {"params", { addr.toStdString() }},
|
||||||
|
// };
|
||||||
|
// return payload;
|
||||||
|
// },
|
||||||
|
// [=] (QMap<QString, json>* privkeys) {
|
||||||
|
// QList<QPair<QString, QString>> allTKeys;
|
||||||
|
// for (QString addr: privkeys->keys()) {
|
||||||
|
// allTKeys.push_back(
|
||||||
|
// QPair<QString, QString>(
|
||||||
|
// addr,
|
||||||
|
// QString::fromStdString(privkeys->value(addr).get<json::string_t>())));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fnCombineTwoLists(allTKeys);
|
||||||
|
// delete privkeys;
|
||||||
|
// }
|
||||||
|
// );
|
||||||
|
// });
|
||||||
|
// };
|
||||||
|
|
||||||
|
// // First get all the t and z addresses.
|
||||||
|
// json payloadT = {
|
||||||
|
// {"jsonrpc", "1.0"},
|
||||||
|
// {"id", "someid"},
|
||||||
|
// {"method", "getaddressesbyaccount"},
|
||||||
|
// {"params", {""} }
|
||||||
|
// };
|
||||||
|
|
||||||
|
// json payloadZ = {
|
||||||
|
// {"jsonrpc", "1.0"},
|
||||||
|
// {"id", "someid"},
|
||||||
|
// {"method", "z_listaddresses"}
|
||||||
|
// };
|
||||||
|
|
||||||
|
// fnDoBatchGetPrivKeys(payloadT, "dumpprivkey");
|
||||||
|
// fnDoBatchGetPrivKeys(payloadZ, "z_exportkey");
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiteInterface::fetchOpStatus(const std::function<void(json)>& cb) {
|
||||||
|
if (conn == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Make an RPC to load pending operation statues
|
||||||
|
// json payload = {
|
||||||
|
// {"jsonrpc", "1.0"},
|
||||||
|
// {"id", "someid"},
|
||||||
|
// {"method", "z_getoperationstatus"},
|
||||||
|
// };
|
||||||
|
|
||||||
|
// conn->doRPCIgnoreError(payload, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiteInterface::fetchReceivedTTrans(QList<QString> txids, QList<TransactionItem> sentZTxs,
|
||||||
|
const std::function<void(QList<TransactionItem>)> txdataFn) {
|
||||||
|
if (conn == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Look up all the txids to get the confirmation count for them.
|
||||||
|
// conn->doBatchRPC<QString>(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 j = txidList->value(sentTx.txid);
|
||||||
|
// if (j.is_null())
|
||||||
|
// continue;
|
||||||
|
// auto error = j["confirmations"].is_null();
|
||||||
|
// if (!error)
|
||||||
|
// sentTx.confirmations = j["confirmations"].get<json::number_integer_t>();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// txdataFn(newSentZTxs);
|
||||||
|
// delete txidList;
|
||||||
|
// }
|
||||||
|
// );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Refresh received z txs by calling z_listreceivedbyaddress/gettransaction
|
||||||
|
void LiteInterface::fetchReceivedZTrans(QList<QString> zaddrs, const std::function<void(QString)> usedAddrFn,
|
||||||
|
const std::function<void(QList<TransactionItem>)> txdataFn) {
|
||||||
|
if (conn == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
|
||||||
|
// This method is complicated because z_listreceivedbyaddress only returns the txid, and
|
||||||
|
// we have to make a follow up call to gettransaction to get details of that transaction.
|
||||||
|
// Additionally, it has to be done in batches, because there are multiple z-Addresses,
|
||||||
|
// and each z-Addr can have multiple received txs.
|
||||||
|
|
||||||
|
// 1. For each z-Addr, get list of received txs
|
||||||
|
// conn->doBatchRPC<QString>(zaddrs,
|
||||||
|
// [=] (QString zaddr) {
|
||||||
|
// json payload = {
|
||||||
|
// {"jsonrpc", "1.0"},
|
||||||
|
// {"id", "z_lrba"},
|
||||||
|
// {"method", "z_listreceivedbyaddress"},
|
||||||
|
// {"params", {zaddr.toStdString(), 0}} // Accept 0 conf as well.
|
||||||
|
// };
|
||||||
|
|
||||||
|
// return payload;
|
||||||
|
// },
|
||||||
|
// [=] (QMap<QString, json>* zaddrTxids) {
|
||||||
|
// // Process all txids, removing duplicates. This can happen if the same address
|
||||||
|
// // appears multiple times in a single tx's outputs.
|
||||||
|
// QSet<QString> txids;
|
||||||
|
// QMap<QString, QString> memos;
|
||||||
|
// for (auto it = zaddrTxids->constBegin(); it != zaddrTxids->constEnd(); it++) {
|
||||||
|
// auto zaddr = it.key();
|
||||||
|
// for (auto& i : it.value().get<json::array_t>()) {
|
||||||
|
// // Mark the address as used
|
||||||
|
// usedAddrFn(zaddr);
|
||||||
|
|
||||||
|
// // Filter out change txs
|
||||||
|
// if (! i["change"].get<json::boolean_t>()) {
|
||||||
|
// auto txid = QString::fromStdString(i["txid"].get<json::string_t>());
|
||||||
|
// txids.insert(txid);
|
||||||
|
|
||||||
|
// // Check for Memos
|
||||||
|
// QString memoBytes = QString::fromStdString(i["memo"].get<json::string_t>());
|
||||||
|
// if (!memoBytes.startsWith("f600")) {
|
||||||
|
// QString memo(QByteArray::fromHex(
|
||||||
|
// QByteArray::fromStdString(i["memo"].get<json::string_t>())));
|
||||||
|
// if (!memo.trimmed().isEmpty())
|
||||||
|
// memos[zaddr + txid] = memo;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // 2. For all txids, go and get the details of that txid.
|
||||||
|
// conn->doBatchRPC<QString>(txids.toList(),
|
||||||
|
// [=] (QString txid) {
|
||||||
|
// json payload = {
|
||||||
|
// {"jsonrpc", "1.0"},
|
||||||
|
// {"id", "gettx"},
|
||||||
|
// {"method", "gettransaction"},
|
||||||
|
// {"params", {txid.toStdString()}}
|
||||||
|
// };
|
||||||
|
|
||||||
|
// return payload;
|
||||||
|
// },
|
||||||
|
// [=] (QMap<QString, json>* txidDetails) {
|
||||||
|
// QList<TransactionItem> txdata;
|
||||||
|
|
||||||
|
// // Combine them both together. For every zAddr's txid, get the amount, fee, confirmations and time
|
||||||
|
// for (auto it = zaddrTxids->constBegin(); it != zaddrTxids->constEnd(); it++) {
|
||||||
|
// for (auto& i : it.value().get<json::array_t>()) {
|
||||||
|
// // Filter out change txs
|
||||||
|
// if (i["change"].get<json::boolean_t>())
|
||||||
|
// continue;
|
||||||
|
|
||||||
|
// auto zaddr = it.key();
|
||||||
|
// auto txid = QString::fromStdString(i["txid"].get<json::string_t>());
|
||||||
|
|
||||||
|
// // Lookup txid in the map
|
||||||
|
// auto txidInfo = txidDetails->value(txid);
|
||||||
|
|
||||||
|
// qint64 timestamp;
|
||||||
|
// if (txidInfo.find("time") != txidInfo.end()) {
|
||||||
|
// timestamp = txidInfo["time"].get<json::number_unsigned_t>();
|
||||||
|
// } else {
|
||||||
|
// timestamp = txidInfo["blocktime"].get<json::number_unsigned_t>();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// auto amount = i["amount"].get<json::number_float_t>();
|
||||||
|
// auto confirmations = static_cast<long>(txidInfo["confirmations"].get<json::number_integer_t>());
|
||||||
|
|
||||||
|
// TransactionItem tx{ QString("receive"), timestamp, zaddr, txid, amount,
|
||||||
|
// confirmations, "", memos.value(zaddr + txid, "") };
|
||||||
|
// txdata.push_front(tx);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// txdataFn(txdata);
|
||||||
|
|
||||||
|
// // Cleanup both responses;
|
||||||
|
// delete zaddrTxids;
|
||||||
|
// delete txidDetails;
|
||||||
|
// }
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// );
|
||||||
|
}
|
||||||
@@ -19,10 +19,10 @@ struct TransactionItem {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
class ZcashdRPC {
|
class LiteInterface {
|
||||||
public:
|
public:
|
||||||
ZcashdRPC();
|
LiteInterface();
|
||||||
~ZcashdRPC();
|
~LiteInterface();
|
||||||
|
|
||||||
bool haveConnection();
|
bool haveConnection();
|
||||||
void setConnection(Connection* c);
|
void setConnection(Connection* c);
|
||||||
@@ -715,29 +715,29 @@ void MainWindow::backupWalletDat() {
|
|||||||
if (!rpc->getConnection())
|
if (!rpc->getConnection())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
QDir zcashdir(rpc->getConnection()->config->zcashDir);
|
// QDir zcashdir(rpc->getConnection()->config->zcashDir);
|
||||||
QString backupDefaultName = "zcash-wallet-backup-" + QDateTime::currentDateTime().toString("yyyyMMdd") + ".dat";
|
// QString backupDefaultName = "zcash-wallet-backup-" + QDateTime::currentDateTime().toString("yyyyMMdd") + ".dat";
|
||||||
|
|
||||||
if (Settings::getInstance()->isTestnet()) {
|
// if (Settings::getInstance()->isTestnet()) {
|
||||||
zcashdir.cd("testnet3");
|
// zcashdir.cd("testnet3");
|
||||||
backupDefaultName = "testnet-" + backupDefaultName;
|
// backupDefaultName = "testnet-" + backupDefaultName;
|
||||||
}
|
// }
|
||||||
|
|
||||||
QFile wallet(zcashdir.filePath("wallet.dat"));
|
// QFile wallet(zcashdir.filePath("wallet.dat"));
|
||||||
if (!wallet.exists()) {
|
// if (!wallet.exists()) {
|
||||||
QMessageBox::critical(this, tr("No wallet.dat"), tr("Couldn't find the wallet.dat on this computer") + "\n" +
|
// QMessageBox::critical(this, tr("No wallet.dat"), tr("Couldn't find the wallet.dat on this computer") + "\n" +
|
||||||
tr("You need to back it up from the machine zcashd is running on"), QMessageBox::Ok);
|
// tr("You need to back it up from the machine zcashd is running on"), QMessageBox::Ok);
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
QUrl backupName = QFileDialog::getSaveFileUrl(this, tr("Backup wallet.dat"), backupDefaultName, "Data file (*.dat)");
|
// QUrl backupName = QFileDialog::getSaveFileUrl(this, tr("Backup wallet.dat"), backupDefaultName, "Data file (*.dat)");
|
||||||
if (backupName.isEmpty())
|
// if (backupName.isEmpty())
|
||||||
return;
|
// return;
|
||||||
|
|
||||||
if (!wallet.copy(backupName.toLocalFile())) {
|
// if (!wallet.copy(backupName.toLocalFile())) {
|
||||||
QMessageBox::critical(this, tr("Couldn't backup"), tr("Couldn't backup the wallet.dat file.") +
|
// QMessageBox::critical(this, tr("Couldn't backup"), tr("Couldn't backup the wallet.dat file.") +
|
||||||
tr("You need to back it up manually."), QMessageBox::Ok);
|
// tr("You need to back it up manually."), QMessageBox::Ok);
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::exportAllKeys() {
|
void MainWindow::exportAllKeys() {
|
||||||
|
|||||||
895
src/rpc.cpp
895
src/rpc.cpp
@@ -1,895 +0,0 @@
|
|||||||
#include "controller.h"
|
|
||||||
|
|
||||||
#include "addressbook.h"
|
|
||||||
#include "settings.h"
|
|
||||||
#include "senttxstore.h"
|
|
||||||
#include "turnstile.h"
|
|
||||||
#include "version.h"
|
|
||||||
#include "websockets.h"
|
|
||||||
|
|
||||||
using json = nlohmann::json;
|
|
||||||
|
|
||||||
Controller::Controller(MainWindow* main) {
|
|
||||||
auto cl = new ConnectionLoader(main, this);
|
|
||||||
|
|
||||||
// Execute the load connection async, so we can set up the rest of RPC properly.
|
|
||||||
QTimer::singleShot(1, [=]() { cl->loadConnection(); });
|
|
||||||
|
|
||||||
this->main = main;
|
|
||||||
this->ui = main->ui;
|
|
||||||
|
|
||||||
// Setup balances table model
|
|
||||||
balancesTableModel = new BalancesTableModel(main->ui->balancesTable);
|
|
||||||
main->ui->balancesTable->setModel(balancesTableModel);
|
|
||||||
|
|
||||||
// Setup transactions table model
|
|
||||||
transactionsTableModel = new TxTableModel(ui->transactionsTable);
|
|
||||||
main->ui->transactionsTable->setModel(transactionsTableModel);
|
|
||||||
|
|
||||||
// Set up timer to refresh Price
|
|
||||||
priceTimer = new QTimer(main);
|
|
||||||
QObject::connect(priceTimer, &QTimer::timeout, [=]() {
|
|
||||||
if (Settings::getInstance()->getAllowFetchPrices())
|
|
||||||
refreshZECPrice();
|
|
||||||
});
|
|
||||||
priceTimer->start(Settings::priceRefreshSpeed); // Every hour
|
|
||||||
|
|
||||||
// Set up a timer to refresh the UI every few seconds
|
|
||||||
timer = new QTimer(main);
|
|
||||||
QObject::connect(timer, &QTimer::timeout, [=]() {
|
|
||||||
refresh();
|
|
||||||
});
|
|
||||||
timer->start(Settings::updateSpeed);
|
|
||||||
|
|
||||||
// Set up the timer to watch for tx status
|
|
||||||
txTimer = new QTimer(main);
|
|
||||||
QObject::connect(txTimer, &QTimer::timeout, [=]() {
|
|
||||||
watchTxStatus();
|
|
||||||
});
|
|
||||||
// Start at every 10s. When an operation is pending, this will change to every second
|
|
||||||
txTimer->start(Settings::updateSpeed);
|
|
||||||
|
|
||||||
// Create the data model
|
|
||||||
model = new DataModel();
|
|
||||||
|
|
||||||
// Crate the ZcashdRPC
|
|
||||||
zrpc = new ZcashdRPC();
|
|
||||||
|
|
||||||
// Initialize the migration status to unavailable.
|
|
||||||
this->migrationStatus.available = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Controller::~Controller() {
|
|
||||||
delete timer;
|
|
||||||
delete txTimer;
|
|
||||||
|
|
||||||
delete transactionsTableModel;
|
|
||||||
delete balancesTableModel;
|
|
||||||
|
|
||||||
delete model;
|
|
||||||
delete zrpc;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Controller::setEZcashd(QProcess* p) {
|
|
||||||
ezcashd = p;
|
|
||||||
|
|
||||||
if (ezcashd && ui->tabWidget->widget(4) == nullptr) {
|
|
||||||
ui->tabWidget->addTab(main->zcashdtab, "zcashd");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called when a connection to zcashd is available.
|
|
||||||
void Controller::setConnection(Connection* c) {
|
|
||||||
if (c == nullptr) return;
|
|
||||||
|
|
||||||
this->zrpc->setConnection(c);
|
|
||||||
|
|
||||||
ui->statusBar->showMessage("Ready!");
|
|
||||||
|
|
||||||
// See if we need to remove the reindex/rescan flags from the zcash.conf file
|
|
||||||
auto zcashConfLocation = Settings::getInstance()->getZcashdConfLocation();
|
|
||||||
Settings::removeFromZcashConf(zcashConfLocation, "rescan");
|
|
||||||
Settings::removeFromZcashConf(zcashConfLocation, "reindex");
|
|
||||||
|
|
||||||
// If we're allowed to get the Zec Price, get the prices
|
|
||||||
if (Settings::getInstance()->getAllowFetchPrices())
|
|
||||||
refreshZECPrice();
|
|
||||||
|
|
||||||
// If we're allowed to check for updates, check for a new release
|
|
||||||
if (Settings::getInstance()->getCheckForUpdates())
|
|
||||||
checkForUpdate();
|
|
||||||
|
|
||||||
// Force update, because this might be coming from a settings update
|
|
||||||
// where we need to immediately refresh
|
|
||||||
refresh(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Build the RPC JSON Parameters for this tx
|
|
||||||
void Controller::fillTxJsonParams(json& params, Tx tx) {
|
|
||||||
Q_ASSERT(params.is_array());
|
|
||||||
// Get all the addresses and amounts
|
|
||||||
json allRecepients = json::array();
|
|
||||||
|
|
||||||
// For each addr/amt/memo, construct the JSON and also build the confirm dialog box
|
|
||||||
for (int i=0; i < tx.toAddrs.size(); i++) {
|
|
||||||
auto toAddr = tx.toAddrs[i];
|
|
||||||
|
|
||||||
// Construct the JSON params
|
|
||||||
json rec = json::object();
|
|
||||||
rec["address"] = toAddr.addr.toStdString();
|
|
||||||
// Force it through string for rounding. Without this, decimal points beyond 8 places
|
|
||||||
// will appear, causing an "invalid amount" error
|
|
||||||
rec["amount"] = Settings::getDecimalString(toAddr.amount).toStdString(); //.toDouble();
|
|
||||||
if (Settings::isZAddress(toAddr.addr) && !toAddr.encodedMemo.trimmed().isEmpty())
|
|
||||||
rec["memo"] = toAddr.encodedMemo.toStdString();
|
|
||||||
|
|
||||||
allRecepients.push_back(rec);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add sender
|
|
||||||
params.push_back(tx.fromAddr.toStdString());
|
|
||||||
params.push_back(allRecepients);
|
|
||||||
|
|
||||||
// Add fees if custom fees are allowed.
|
|
||||||
if (Settings::getInstance()->getAllowCustomFees()) {
|
|
||||||
params.push_back(1); // minconf
|
|
||||||
params.push_back(tx.fee);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Controller::noConnection() {
|
|
||||||
QIcon i = QApplication::style()->standardIcon(QStyle::SP_MessageBoxCritical);
|
|
||||||
main->statusIcon->setPixmap(i.pixmap(16, 16));
|
|
||||||
main->statusIcon->setToolTip("");
|
|
||||||
main->statusLabel->setText(QObject::tr("No Connection"));
|
|
||||||
main->statusLabel->setToolTip("");
|
|
||||||
main->ui->statusBar->showMessage(QObject::tr("No Connection"), 1000);
|
|
||||||
|
|
||||||
// Clear balances table.
|
|
||||||
QMap<QString, double> emptyBalances;
|
|
||||||
QList<UnspentOutput> emptyOutputs;
|
|
||||||
balancesTableModel->setNewData(emptyBalances, emptyOutputs);
|
|
||||||
|
|
||||||
// Clear Transactions table.
|
|
||||||
QList<TransactionItem> emptyTxs;
|
|
||||||
transactionsTableModel->addTData(emptyTxs);
|
|
||||||
transactionsTableModel->addZRecvData(emptyTxs);
|
|
||||||
transactionsTableModel->addZSentData(emptyTxs);
|
|
||||||
|
|
||||||
// Clear balances
|
|
||||||
ui->balSheilded->setText("");
|
|
||||||
ui->balTransparent->setText("");
|
|
||||||
ui->balTotal->setText("");
|
|
||||||
|
|
||||||
ui->balSheilded->setToolTip("");
|
|
||||||
ui->balTransparent->setToolTip("");
|
|
||||||
ui->balTotal->setToolTip("");
|
|
||||||
|
|
||||||
// Clear send tab from address
|
|
||||||
ui->inputsCombo->clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Refresh received z txs by calling z_listreceivedbyaddress/gettransaction
|
|
||||||
void Controller::refreshReceivedZTrans(QList<QString> zaddrs) {
|
|
||||||
if (!zrpc->haveConnection())
|
|
||||||
return noConnection();
|
|
||||||
|
|
||||||
// We'll only refresh the received Z txs if settings allows us.
|
|
||||||
if (!Settings::getInstance()->getSaveZtxs()) {
|
|
||||||
QList<TransactionItem> emptylist;
|
|
||||||
transactionsTableModel->addZRecvData(emptylist);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
zrpc->fetchReceivedZTrans(zaddrs,
|
|
||||||
[=] (QString addr) {
|
|
||||||
model->markAddressUsed(addr);
|
|
||||||
},
|
|
||||||
[=] (QList<TransactionItem> txdata) {
|
|
||||||
transactionsTableModel->addZRecvData(txdata);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This will refresh all the balance data from zcashd
|
|
||||||
void Controller::refresh(bool force) {
|
|
||||||
if (!zrpc->haveConnection())
|
|
||||||
return noConnection();
|
|
||||||
|
|
||||||
getInfoThenRefresh(force);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Controller::getInfoThenRefresh(bool force) {
|
|
||||||
if (!zrpc->haveConnection())
|
|
||||||
return noConnection();
|
|
||||||
|
|
||||||
static bool prevCallSucceeded = false;
|
|
||||||
|
|
||||||
zrpc->fetchInfo([=] (const json& reply) {
|
|
||||||
prevCallSucceeded = true;
|
|
||||||
// Testnet?
|
|
||||||
if (!reply["testnet"].is_null()) {
|
|
||||||
Settings::getInstance()->setTestnet(reply["testnet"].get<json::boolean_t>());
|
|
||||||
};
|
|
||||||
|
|
||||||
// Recurring pamynets are testnet only
|
|
||||||
if (!Settings::getInstance()->isTestnet())
|
|
||||||
main->disableRecurring();
|
|
||||||
|
|
||||||
// Connected, so display checkmark.
|
|
||||||
QIcon i(":/icons/res/connected.gif");
|
|
||||||
main->statusIcon->setPixmap(i.pixmap(16, 16));
|
|
||||||
|
|
||||||
static int lastBlock = 0;
|
|
||||||
int curBlock = reply["blocks"].get<json::number_integer_t>();
|
|
||||||
int version = reply["version"].get<json::number_integer_t>();
|
|
||||||
Settings::getInstance()->setZcashdVersion(version);
|
|
||||||
|
|
||||||
// See if recurring payments needs anything
|
|
||||||
Recurring::getInstance()->processPending(main);
|
|
||||||
|
|
||||||
if ( force || (curBlock != lastBlock) ) {
|
|
||||||
// Something changed, so refresh everything.
|
|
||||||
lastBlock = curBlock;
|
|
||||||
|
|
||||||
refreshBalances();
|
|
||||||
refreshAddresses(); // This calls refreshZSentTransactions() and refreshReceivedZTrans()
|
|
||||||
refreshTransactions();
|
|
||||||
refreshMigration(); // Sapling turnstile migration status.
|
|
||||||
}
|
|
||||||
|
|
||||||
int connections = reply["connections"].get<json::number_integer_t>();
|
|
||||||
Settings::getInstance()->setPeers(connections);
|
|
||||||
|
|
||||||
if (connections == 0) {
|
|
||||||
// If there are no peers connected, then the internet is probably off or something else is wrong.
|
|
||||||
QIcon i = QApplication::style()->standardIcon(QStyle::SP_MessageBoxWarning);
|
|
||||||
main->statusIcon->setPixmap(i.pixmap(16, 16));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get network sol/s
|
|
||||||
if (ezcashd) {
|
|
||||||
zrpc->fetchNetSolOps([=] (qint64 solrate) {
|
|
||||||
ui->numconnections->setText(QString::number(connections));
|
|
||||||
ui->solrate->setText(QString::number(solrate) % " Sol/s");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call to see if the blockchain is syncing.
|
|
||||||
zrpc->fetchBlockchainInfo([=](const json& reply) {
|
|
||||||
auto progress = reply["verificationprogress"].get<double>();
|
|
||||||
bool isSyncing = progress < 0.9999; // 99.99%
|
|
||||||
int blockNumber = reply["blocks"].get<json::number_unsigned_t>();
|
|
||||||
|
|
||||||
int estimatedheight = 0;
|
|
||||||
if (reply.find("estimatedheight") != reply.end()) {
|
|
||||||
estimatedheight = reply["estimatedheight"].get<json::number_unsigned_t>();
|
|
||||||
}
|
|
||||||
|
|
||||||
Settings::getInstance()->setSyncing(isSyncing);
|
|
||||||
Settings::getInstance()->setBlockNumber(blockNumber);
|
|
||||||
|
|
||||||
// Update zcashd tab if it exists
|
|
||||||
if (ezcashd) {
|
|
||||||
if (isSyncing) {
|
|
||||||
QString txt = QString::number(blockNumber);
|
|
||||||
if (estimatedheight > 0) {
|
|
||||||
txt = txt % " / ~" % QString::number(estimatedheight);
|
|
||||||
}
|
|
||||||
txt = txt % " ( " % QString::number(progress * 100, 'f', 2) % "% )";
|
|
||||||
ui->blockheight->setText(txt);
|
|
||||||
ui->heightLabel->setText(QObject::tr("Downloading blocks"));
|
|
||||||
} else {
|
|
||||||
// If syncing is finished, we may have to remove the ibdskiptxverification
|
|
||||||
// flag from zcash.conf
|
|
||||||
if (getConnection() != nullptr && getConnection()->config->skiptxverification) {
|
|
||||||
getConnection()->config->skiptxverification = false;
|
|
||||||
Settings::removeFromZcashConf(Settings::getInstance()->getZcashdConfLocation(),
|
|
||||||
"ibdskiptxverification");
|
|
||||||
}
|
|
||||||
|
|
||||||
ui->blockheight->setText(QString::number(blockNumber));
|
|
||||||
ui->heightLabel->setText(QObject::tr("Block height"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the status bar
|
|
||||||
QString statusText = QString() %
|
|
||||||
(isSyncing ? QObject::tr("Syncing") : QObject::tr("Connected")) %
|
|
||||||
" (" %
|
|
||||||
(Settings::getInstance()->isTestnet() ? QObject::tr("testnet:") : "") %
|
|
||||||
QString::number(blockNumber) %
|
|
||||||
(isSyncing ? ("/" % QString::number(progress*100, 'f', 2) % "%") : QString()) %
|
|
||||||
")";
|
|
||||||
main->statusLabel->setText(statusText);
|
|
||||||
|
|
||||||
// Update the balances view to show a warning if the node is still syncing
|
|
||||||
ui->lblSyncWarning->setVisible(isSyncing);
|
|
||||||
ui->lblSyncWarningReceive->setVisible(isSyncing);
|
|
||||||
|
|
||||||
auto zecPrice = Settings::getInstance()->getUSDFromZecAmount(1);
|
|
||||||
QString tooltip;
|
|
||||||
if (connections > 0) {
|
|
||||||
tooltip = QObject::tr("Connected to zcashd");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
tooltip = QObject::tr("zcashd has no peer connections");
|
|
||||||
}
|
|
||||||
tooltip = tooltip % "(v " % QString::number(Settings::getInstance()->getZcashdVersion()) % ")";
|
|
||||||
|
|
||||||
if (!zecPrice.isEmpty()) {
|
|
||||||
tooltip = "1 " % Settings::getTokenName() % " = " % zecPrice % "\n" % tooltip;
|
|
||||||
}
|
|
||||||
main->statusLabel->setToolTip(tooltip);
|
|
||||||
main->statusIcon->setToolTip(tooltip);
|
|
||||||
});
|
|
||||||
|
|
||||||
}, [=](QNetworkReply* reply, const json&) {
|
|
||||||
// zcashd has probably disappeared.
|
|
||||||
this->noConnection();
|
|
||||||
|
|
||||||
// Prevent multiple dialog boxes, because these are called async
|
|
||||||
static bool shown = false;
|
|
||||||
if (!shown && prevCallSucceeded) { // show error only first time
|
|
||||||
shown = true;
|
|
||||||
QMessageBox::critical(main, QObject::tr("Connection Error"), QObject::tr("There was an error connecting to zcashd. The error was") + ": \n\n"
|
|
||||||
+ reply->errorString(), QMessageBox::StandardButton::Ok);
|
|
||||||
shown = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
prevCallSucceeded = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void Controller::refreshAddresses() {
|
|
||||||
if (!zrpc->haveConnection())
|
|
||||||
return noConnection();
|
|
||||||
|
|
||||||
auto newzaddresses = new QList<QString>();
|
|
||||||
|
|
||||||
zrpc->fetchZAddresses([=] (json reply) {
|
|
||||||
for (auto& it : reply.get<json::array_t>()) {
|
|
||||||
auto addr = QString::fromStdString(it.get<json::string_t>());
|
|
||||||
newzaddresses->push_back(addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
model->replaceZaddresses(newzaddresses);
|
|
||||||
|
|
||||||
// Refresh the sent and received txs from all these z-addresses
|
|
||||||
refreshSentZTrans();
|
|
||||||
refreshReceivedZTrans(model->getAllZAddresses());
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
auto newtaddresses = new QList<QString>();
|
|
||||||
zrpc->fetchTAddresses([=] (json reply) {
|
|
||||||
for (auto& it : reply.get<json::array_t>()) {
|
|
||||||
auto addr = QString::fromStdString(it.get<json::string_t>());
|
|
||||||
if (Settings::isTAddress(addr))
|
|
||||||
newtaddresses->push_back(addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
model->replaceTaddresses(newtaddresses);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to create the data model and update the views, used below.
|
|
||||||
void Controller::updateUI(bool anyUnconfirmed) {
|
|
||||||
ui->unconfirmedWarning->setVisible(anyUnconfirmed);
|
|
||||||
|
|
||||||
// Update balances model data, which will update the table too
|
|
||||||
balancesTableModel->setNewData(model->getAllBalances(), model->getUTXOs());
|
|
||||||
|
|
||||||
// Update from address
|
|
||||||
main->updateFromCombo();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Function to process reply of the listunspent and z_listunspent API calls, used below.
|
|
||||||
bool Controller::processUnspent(const json& reply, QMap<QString, double>* balancesMap, QList<UnspentOutput>* newUtxos) {
|
|
||||||
bool anyUnconfirmed = false;
|
|
||||||
for (auto& it : reply.get<json::array_t>()) {
|
|
||||||
QString qsAddr = QString::fromStdString(it["address"]);
|
|
||||||
auto confirmations = it["confirmations"].get<json::number_unsigned_t>();
|
|
||||||
if (confirmations == 0) {
|
|
||||||
anyUnconfirmed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
newUtxos->push_back(
|
|
||||||
UnspentOutput{ qsAddr, QString::fromStdString(it["txid"]),
|
|
||||||
Settings::getDecimalString(it["amount"].get<json::number_float_t>()),
|
|
||||||
(int)confirmations, it["spendable"].get<json::boolean_t>() });
|
|
||||||
|
|
||||||
(*balancesMap)[qsAddr] = (*balancesMap)[qsAddr] + it["amount"].get<json::number_float_t>();
|
|
||||||
}
|
|
||||||
return anyUnconfirmed;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Refresh the turnstile migration status
|
|
||||||
*/
|
|
||||||
void Controller::refreshMigration() {
|
|
||||||
// Turnstile migration is only supported in zcashd v2.0.5 and above
|
|
||||||
if (Settings::getInstance()->getZcashdVersion() < 2000552)
|
|
||||||
return;
|
|
||||||
|
|
||||||
zrpc->fetchMigrationStatus([=](json reply) {
|
|
||||||
this->migrationStatus.available = true;
|
|
||||||
this->migrationStatus.enabled = reply["enabled"].get<json::boolean_t>();
|
|
||||||
this->migrationStatus.saplingAddress = QString::fromStdString(reply["destination_address"]);
|
|
||||||
this->migrationStatus.unmigrated = QString::fromStdString(reply["unmigrated_amount"]).toDouble();
|
|
||||||
this->migrationStatus.migrated = QString::fromStdString(reply["finalized_migrated_amount"]).toDouble();
|
|
||||||
|
|
||||||
QList<QString> ids;
|
|
||||||
for (auto& it : reply["migration_txids"].get<json::array_t>()) {
|
|
||||||
ids.push_back(QString::fromStdString(it.get<json::string_t>()));
|
|
||||||
}
|
|
||||||
this->migrationStatus.txids = ids;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void Controller::refreshBalances() {
|
|
||||||
if (!zrpc->haveConnection())
|
|
||||||
return noConnection();
|
|
||||||
|
|
||||||
// 1. Get the Balances
|
|
||||||
zrpc->fetchBalance([=] (json reply) {
|
|
||||||
auto balT = QString::fromStdString(reply["transparent"]).toDouble();
|
|
||||||
auto balZ = QString::fromStdString(reply["private"]).toDouble();
|
|
||||||
auto balTotal = QString::fromStdString(reply["total"]).toDouble();
|
|
||||||
|
|
||||||
AppDataModel::getInstance()->setBalances(balT, balZ);
|
|
||||||
|
|
||||||
ui->balSheilded ->setText(Settings::getZECDisplayFormat(balZ));
|
|
||||||
ui->balTransparent->setText(Settings::getZECDisplayFormat(balT));
|
|
||||||
ui->balTotal ->setText(Settings::getZECDisplayFormat(balTotal));
|
|
||||||
|
|
||||||
|
|
||||||
ui->balSheilded ->setToolTip(Settings::getZECDisplayFormat(balZ));
|
|
||||||
ui->balTransparent->setToolTip(Settings::getZECDisplayFormat(balT));
|
|
||||||
ui->balTotal ->setToolTip(Settings::getZECDisplayFormat(balTotal));
|
|
||||||
});
|
|
||||||
|
|
||||||
// 2. Get the UTXOs
|
|
||||||
// First, create a new UTXO list. It will be replacing the existing list when everything is processed.
|
|
||||||
auto newUtxos = new QList<UnspentOutput>();
|
|
||||||
auto newBalances = new QMap<QString, double>();
|
|
||||||
|
|
||||||
// Call the Transparent and Z unspent APIs serially and then, once they're done, update the UI
|
|
||||||
zrpc->fetchTransparentUnspent([=] (json reply) {
|
|
||||||
auto anyTUnconfirmed = processUnspent(reply, newBalances, newUtxos);
|
|
||||||
|
|
||||||
zrpc->fetchZUnspent([=] (json reply) {
|
|
||||||
auto anyZUnconfirmed = processUnspent(reply, newBalances, newUtxos);
|
|
||||||
|
|
||||||
// Swap out the balances and UTXOs
|
|
||||||
model->replaceBalances(newBalances);
|
|
||||||
model->replaceUTXOs(newUtxos);
|
|
||||||
|
|
||||||
updateUI(anyTUnconfirmed || anyZUnconfirmed);
|
|
||||||
|
|
||||||
main->balancesReady();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void Controller::refreshTransactions() {
|
|
||||||
if (!zrpc->haveConnection())
|
|
||||||
return noConnection();
|
|
||||||
|
|
||||||
zrpc->fetchTransactions([=] (json reply) {
|
|
||||||
QList<TransactionItem> txdata;
|
|
||||||
|
|
||||||
for (auto& it : reply.get<json::array_t>()) {
|
|
||||||
double fee = 0;
|
|
||||||
if (!it["fee"].is_null()) {
|
|
||||||
fee = it["fee"].get<json::number_float_t>();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString address = (it["address"].is_null() ? "" : QString::fromStdString(it["address"]));
|
|
||||||
|
|
||||||
TransactionItem tx{
|
|
||||||
QString::fromStdString(it["category"]),
|
|
||||||
(qint64)it["time"].get<json::number_unsigned_t>(),
|
|
||||||
address,
|
|
||||||
QString::fromStdString(it["txid"]),
|
|
||||||
it["amount"].get<json::number_float_t>() + fee,
|
|
||||||
static_cast<long>(it["confirmations"].get<json::number_unsigned_t>()),
|
|
||||||
"", "" };
|
|
||||||
|
|
||||||
txdata.push_back(tx);
|
|
||||||
if (!address.isEmpty())
|
|
||||||
model->markAddressUsed(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update model data, which updates the table view
|
|
||||||
transactionsTableModel->addTData(txdata);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read sent Z transactions from the file.
|
|
||||||
void Controller::refreshSentZTrans() {
|
|
||||||
if (!zrpc->haveConnection())
|
|
||||||
return noConnection();
|
|
||||||
|
|
||||||
auto sentZTxs = SentTxStore::readSentTxFile();
|
|
||||||
|
|
||||||
// If there are no sent z txs, then empty the table.
|
|
||||||
// This happens when you clear history.
|
|
||||||
if (sentZTxs.isEmpty()) {
|
|
||||||
transactionsTableModel->addZSentData(sentZTxs);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QList<QString> txids;
|
|
||||||
|
|
||||||
for (auto sentTx: sentZTxs) {
|
|
||||||
txids.push_back(sentTx.txid);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Look up all the txids to get the confirmation count for them.
|
|
||||||
zrpc->fetchReceivedTTrans(txids, sentZTxs, [=](auto newSentZTxs) {
|
|
||||||
transactionsTableModel->addZSentData(newSentZTxs);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void Controller::addNewTxToWatch(const QString& newOpid, WatchedTx wtx) {
|
|
||||||
watchingOps.insert(newOpid, wtx);
|
|
||||||
|
|
||||||
watchTxStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute a transaction with the standard UI. i.e., standard status bar message and standard error
|
|
||||||
* handling
|
|
||||||
*/
|
|
||||||
void Controller::executeStandardUITransaction(Tx tx) {
|
|
||||||
executeTransaction(tx,
|
|
||||||
[=] (QString opid) {
|
|
||||||
ui->statusBar->showMessage(QObject::tr("Computing Tx: ") % opid);
|
|
||||||
},
|
|
||||||
[=] (QString, QString txid) {
|
|
||||||
ui->statusBar->showMessage(Settings::txidStatusMessage + " " + txid);
|
|
||||||
},
|
|
||||||
[=] (QString opid, QString errStr) {
|
|
||||||
ui->statusBar->showMessage(QObject::tr(" Tx ") % opid % QObject::tr(" failed"), 15 * 1000);
|
|
||||||
|
|
||||||
if (!opid.isEmpty())
|
|
||||||
errStr = QObject::tr("The transaction with id ") % opid % QObject::tr(" failed. The error was") + ":\n\n" + errStr;
|
|
||||||
|
|
||||||
QMessageBox::critical(main, QObject::tr("Transaction Error"), errStr, QMessageBox::Ok);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Execute a transaction!
|
|
||||||
void Controller::executeTransaction(Tx tx,
|
|
||||||
const std::function<void(QString opid)> submitted,
|
|
||||||
const std::function<void(QString opid, QString txid)> computed,
|
|
||||||
const std::function<void(QString opid, QString errStr)> error) {
|
|
||||||
// First, create the json params
|
|
||||||
json params = json::array();
|
|
||||||
fillTxJsonParams(params, tx);
|
|
||||||
std::cout << std::setw(2) << params << std::endl;
|
|
||||||
|
|
||||||
zrpc->sendZTransaction(params, [=](const json& reply) {
|
|
||||||
QString opid = QString::fromStdString(reply.get<json::string_t>());
|
|
||||||
|
|
||||||
// And then start monitoring the transaction
|
|
||||||
addNewTxToWatch( opid, WatchedTx { opid, tx, computed, error} );
|
|
||||||
submitted(opid);
|
|
||||||
},
|
|
||||||
[=](QString errStr) {
|
|
||||||
error("", errStr);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Controller::watchTxStatus() {
|
|
||||||
if (!zrpc->haveConnection())
|
|
||||||
return noConnection();
|
|
||||||
|
|
||||||
zrpc->fetchOpStatus([=] (const json& reply) {
|
|
||||||
// There's an array for each item in the status
|
|
||||||
for (auto& it : reply.get<json::array_t>()) {
|
|
||||||
// If we were watching this Tx and its status became "success", then we'll show a status bar alert
|
|
||||||
QString id = QString::fromStdString(it["id"]);
|
|
||||||
if (watchingOps.contains(id)) {
|
|
||||||
// And if it ended up successful
|
|
||||||
QString status = QString::fromStdString(it["status"]);
|
|
||||||
main->loadingLabel->setVisible(false);
|
|
||||||
|
|
||||||
if (status == "success") {
|
|
||||||
auto txid = QString::fromStdString(it["result"]["txid"]);
|
|
||||||
|
|
||||||
SentTxStore::addToSentTx(watchingOps[id].tx, txid);
|
|
||||||
|
|
||||||
auto wtx = watchingOps[id];
|
|
||||||
watchingOps.remove(id);
|
|
||||||
wtx.completed(id, txid);
|
|
||||||
|
|
||||||
// Refresh balances to show unconfirmed balances
|
|
||||||
refresh(true);
|
|
||||||
} else if (status == "failed") {
|
|
||||||
// If it failed, then we'll actually show a warning.
|
|
||||||
auto errorMsg = QString::fromStdString(it["error"]["message"]);
|
|
||||||
|
|
||||||
auto wtx = watchingOps[id];
|
|
||||||
watchingOps.remove(id);
|
|
||||||
wtx.error(id, errorMsg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (watchingOps.isEmpty()) {
|
|
||||||
txTimer->start(Settings::updateSpeed);
|
|
||||||
} else {
|
|
||||||
txTimer->start(Settings::quickUpdateSpeed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there is some op that we are watching, then show the loading bar, otherwise hide it
|
|
||||||
if (watchingOps.empty()) {
|
|
||||||
main->loadingLabel->setVisible(false);
|
|
||||||
} else {
|
|
||||||
main->loadingLabel->setVisible(true);
|
|
||||||
main->loadingLabel->setToolTip(QString::number(watchingOps.size()) + QObject::tr(" tx computing. This can take several minutes."));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void Controller::checkForUpdate(bool silent) {
|
|
||||||
if (!zrpc->haveConnection())
|
|
||||||
return noConnection();
|
|
||||||
|
|
||||||
QUrl cmcURL("https://api.github.com/repos/ZcashFoundation/zecwallet/releases");
|
|
||||||
|
|
||||||
QNetworkRequest req;
|
|
||||||
req.setUrl(cmcURL);
|
|
||||||
|
|
||||||
QNetworkReply *reply = getConnection()->restclient->get(req);
|
|
||||||
|
|
||||||
QObject::connect(reply, &QNetworkReply::finished, [=] {
|
|
||||||
reply->deleteLater();
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (reply->error() == QNetworkReply::NoError) {
|
|
||||||
|
|
||||||
auto releases = QJsonDocument::fromJson(reply->readAll()).array();
|
|
||||||
QVersionNumber maxVersion(0, 0, 0);
|
|
||||||
|
|
||||||
for (QJsonValue rel : releases) {
|
|
||||||
if (!rel.toObject().contains("tag_name"))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
QString tag = rel.toObject()["tag_name"].toString();
|
|
||||||
if (tag.startsWith("v"))
|
|
||||||
tag = tag.right(tag.length() - 1);
|
|
||||||
|
|
||||||
if (!tag.isEmpty()) {
|
|
||||||
auto v = QVersionNumber::fromString(tag);
|
|
||||||
if (v > maxVersion)
|
|
||||||
maxVersion = v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto currentVersion = QVersionNumber::fromString(APP_VERSION);
|
|
||||||
|
|
||||||
// Get the max version that the user has hidden updates for
|
|
||||||
QSettings s;
|
|
||||||
auto maxHiddenVersion = QVersionNumber::fromString(s.value("update/lastversion", "0.0.0").toString());
|
|
||||||
|
|
||||||
qDebug() << "Version check: Current " << currentVersion << ", Available " << maxVersion;
|
|
||||||
|
|
||||||
if (maxVersion > currentVersion && (!silent || maxVersion > maxHiddenVersion)) {
|
|
||||||
auto ans = QMessageBox::information(main, QObject::tr("Update Available"),
|
|
||||||
QObject::tr("A new release v%1 is available! You have v%2.\n\nWould you like to visit the releases page?")
|
|
||||||
.arg(maxVersion.toString())
|
|
||||||
.arg(currentVersion.toString()),
|
|
||||||
QMessageBox::Yes, QMessageBox::Cancel);
|
|
||||||
if (ans == QMessageBox::Yes) {
|
|
||||||
QDesktopServices::openUrl(QUrl("https://github.com/ZcashFoundation/zecwallet/releases"));
|
|
||||||
} else {
|
|
||||||
// If the user selects cancel, don't bother them again for this version
|
|
||||||
s.setValue("update/lastversion", maxVersion.toString());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!silent) {
|
|
||||||
QMessageBox::information(main, QObject::tr("No updates available"),
|
|
||||||
QObject::tr("You already have the latest release v%1")
|
|
||||||
.arg(currentVersion.toString()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (...) {
|
|
||||||
// If anything at all goes wrong, just set the price to 0 and move on.
|
|
||||||
qDebug() << QString("Caught something nasty");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the ZEC->USD price from coinmarketcap using their API
|
|
||||||
void Controller::refreshZECPrice() {
|
|
||||||
if (!zrpc->haveConnection())
|
|
||||||
return noConnection();
|
|
||||||
|
|
||||||
QUrl cmcURL("https://api.coinmarketcap.com/v1/ticker/");
|
|
||||||
|
|
||||||
QNetworkRequest req;
|
|
||||||
req.setUrl(cmcURL);
|
|
||||||
|
|
||||||
QNetworkReply *reply = getConnection()->restclient->get(req);
|
|
||||||
|
|
||||||
QObject::connect(reply, &QNetworkReply::finished, [=] {
|
|
||||||
reply->deleteLater();
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (reply->error() != QNetworkReply::NoError) {
|
|
||||||
auto parsed = json::parse(reply->readAll(), nullptr, false);
|
|
||||||
if (!parsed.is_discarded() && !parsed["error"]["message"].is_null()) {
|
|
||||||
qDebug() << QString::fromStdString(parsed["error"]["message"]);
|
|
||||||
} else {
|
|
||||||
qDebug() << reply->errorString();
|
|
||||||
}
|
|
||||||
Settings::getInstance()->setZECPrice(0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto all = reply->readAll();
|
|
||||||
|
|
||||||
auto parsed = json::parse(all, nullptr, false);
|
|
||||||
if (parsed.is_discarded()) {
|
|
||||||
Settings::getInstance()->setZECPrice(0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const json& item : parsed.get<json::array_t>()) {
|
|
||||||
if (item["symbol"].get<json::string_t>() == Settings::getTokenName().toStdString()) {
|
|
||||||
QString price = QString::fromStdString(item["price_usd"].get<json::string_t>());
|
|
||||||
qDebug() << Settings::getTokenName() << " Price=" << price;
|
|
||||||
Settings::getInstance()->setZECPrice(price.toDouble());
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (...) {
|
|
||||||
// If anything at all goes wrong, just set the price to 0 and move on.
|
|
||||||
qDebug() << QString("Caught something nasty");
|
|
||||||
}
|
|
||||||
|
|
||||||
// If nothing, then set the price to 0;
|
|
||||||
Settings::getInstance()->setZECPrice(0);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void Controller::shutdownZcashd() {
|
|
||||||
// Shutdown embedded zcashd if it was started
|
|
||||||
if (ezcashd == nullptr || ezcashd->processId() == 0 || ~zrpc->haveConnection()) {
|
|
||||||
// No zcashd running internally, just return
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
json payload = {
|
|
||||||
{"jsonrpc", "1.0"},
|
|
||||||
{"id", "someid"},
|
|
||||||
{"method", "stop"}
|
|
||||||
};
|
|
||||||
|
|
||||||
getConnection()->doRPCWithDefaultErrorHandling(payload, [=](auto) {});
|
|
||||||
getConnection()->shutdown();
|
|
||||||
|
|
||||||
QDialog d(main);
|
|
||||||
Ui_ConnectionDialog connD;
|
|
||||||
connD.setupUi(&d);
|
|
||||||
connD.topIcon->setBasePixmap(QIcon(":/icons/res/icon.ico").pixmap(256, 256));
|
|
||||||
connD.status->setText(QObject::tr("Please wait for ZecWallet to exit"));
|
|
||||||
connD.statusDetail->setText(QObject::tr("Waiting for zcashd to exit"));
|
|
||||||
|
|
||||||
QTimer waiter(main);
|
|
||||||
|
|
||||||
// We capture by reference all the local variables because of the d.exec()
|
|
||||||
// below, which blocks this function until we exit.
|
|
||||||
int waitCount = 0;
|
|
||||||
QObject::connect(&waiter, &QTimer::timeout, [&] () {
|
|
||||||
waitCount++;
|
|
||||||
|
|
||||||
if ((ezcashd->atEnd() && ezcashd->processId() == 0) ||
|
|
||||||
waitCount > 30 ||
|
|
||||||
getConnection()->config->zcashDaemon) { // If zcashd is daemon, then we don't have to do anything else
|
|
||||||
qDebug() << "Ended";
|
|
||||||
waiter.stop();
|
|
||||||
QTimer::singleShot(1000, [&]() { d.accept(); });
|
|
||||||
} else {
|
|
||||||
qDebug() << "Not ended, continuing to wait...";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
waiter.start(1000);
|
|
||||||
|
|
||||||
// Wait for the zcash process to exit.
|
|
||||||
if (!Settings::getInstance()->isHeadless()) {
|
|
||||||
d.exec();
|
|
||||||
} else {
|
|
||||||
while (waiter.isActive()) {
|
|
||||||
QCoreApplication::processEvents();
|
|
||||||
|
|
||||||
QThread::sleep(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// // Fetch the Z-board topics list
|
|
||||||
// void Controller::getZboardTopics(std::function<void(QMap<QString, QString>)> cb) {
|
|
||||||
// if (!zrpc->haveConnection())
|
|
||||||
// return noConnection();
|
|
||||||
|
|
||||||
// QUrl cmcURL("http://z-board.net/listTopics");
|
|
||||||
|
|
||||||
// QNetworkRequest req;
|
|
||||||
// req.setUrl(cmcURL);
|
|
||||||
|
|
||||||
// QNetworkReply *reply = conn->restclient->get(req);
|
|
||||||
|
|
||||||
// QObject::connect(reply, &QNetworkReply::finished, [=] {
|
|
||||||
// reply->deleteLater();
|
|
||||||
|
|
||||||
// try {
|
|
||||||
// if (reply->error() != QNetworkReply::NoError) {
|
|
||||||
// auto parsed = json::parse(reply->readAll(), nullptr, false);
|
|
||||||
// if (!parsed.is_discarded() && !parsed["error"]["message"].is_null()) {
|
|
||||||
// qDebug() << QString::fromStdString(parsed["error"]["message"]);
|
|
||||||
// }
|
|
||||||
// else {
|
|
||||||
// qDebug() << reply->errorString();
|
|
||||||
// }
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// auto all = reply->readAll();
|
|
||||||
|
|
||||||
// auto parsed = json::parse(all, nullptr, false);
|
|
||||||
// if (parsed.is_discarded()) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// QMap<QString, QString> topics;
|
|
||||||
// for (const json& item : parsed["topics"].get<json::array_t>()) {
|
|
||||||
// if (item.find("addr") == item.end() || item.find("topicName") == item.end())
|
|
||||||
// return;
|
|
||||||
|
|
||||||
// QString addr = QString::fromStdString(item["addr"].get<json::string_t>());
|
|
||||||
// QString topic = QString::fromStdString(item["topicName"].get<json::string_t>());
|
|
||||||
|
|
||||||
// topics.insert(topic, addr);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// cb(topics);
|
|
||||||
// }
|
|
||||||
// catch (...) {
|
|
||||||
// // If anything at all goes wrong, just set the price to 0 and move on.
|
|
||||||
// qDebug() << QString("Caught something nasty");
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a Sapling address from the user's wallet
|
|
||||||
*/
|
|
||||||
QString Controller::getDefaultSaplingAddress() {
|
|
||||||
for (QString addr: model->getAllZAddresses()) {
|
|
||||||
if (Settings::getInstance()->isSaplingAddress(addr))
|
|
||||||
return addr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return QString();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString Controller::getDefaultTAddress() {
|
|
||||||
if (model->getAllTAddresses().length() > 0)
|
|
||||||
return model->getAllTAddresses().at(0);
|
|
||||||
else
|
|
||||||
return QString();
|
|
||||||
}
|
|
||||||
@@ -1,545 +0,0 @@
|
|||||||
#include "zcashdrpc.h"
|
|
||||||
|
|
||||||
ZcashdRPC::ZcashdRPC() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
ZcashdRPC::~ZcashdRPC() {
|
|
||||||
delete conn;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ZcashdRPC::setConnection(Connection* c) {
|
|
||||||
if (conn) {
|
|
||||||
delete conn;
|
|
||||||
}
|
|
||||||
|
|
||||||
conn = c;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ZcashdRPC::haveConnection() {
|
|
||||||
return conn != nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ZcashdRPC::fetchTAddresses(const std::function<void(json)>& cb) {
|
|
||||||
if (conn == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
json payload = {
|
|
||||||
{"jsonrpc", "1.0"},
|
|
||||||
{"id", "someid"},
|
|
||||||
{"method", "getaddressesbyaccount"},
|
|
||||||
{"params", {""}}
|
|
||||||
};
|
|
||||||
|
|
||||||
conn->doRPCWithDefaultErrorHandling(payload, cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ZcashdRPC::fetchZAddresses(const std::function<void(json)>& cb) {
|
|
||||||
if (conn == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
json payload = {
|
|
||||||
{"jsonrpc", "1.0"},
|
|
||||||
{"id", "someid"},
|
|
||||||
{"method", "z_listaddresses"},
|
|
||||||
};
|
|
||||||
|
|
||||||
conn->doRPCWithDefaultErrorHandling(payload, cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ZcashdRPC::fetchTransparentUnspent(const std::function<void(json)>& cb) {
|
|
||||||
if (conn == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
json payload = {
|
|
||||||
{"jsonrpc", "1.0"},
|
|
||||||
{"id", "someid"},
|
|
||||||
{"method", "listunspent"},
|
|
||||||
{"params", {0}} // Get UTXOs with 0 confirmations as well.
|
|
||||||
};
|
|
||||||
|
|
||||||
conn->doRPCWithDefaultErrorHandling(payload, cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ZcashdRPC::fetchZUnspent(const std::function<void(json)>& cb) {
|
|
||||||
if (conn == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
json payload = {
|
|
||||||
{"jsonrpc", "1.0"},
|
|
||||||
{"id", "someid"},
|
|
||||||
{"method", "z_listunspent"},
|
|
||||||
{"params", {0}} // Get UTXOs with 0 confirmations as well.
|
|
||||||
};
|
|
||||||
|
|
||||||
conn->doRPCWithDefaultErrorHandling(payload, cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ZcashdRPC::createNewZaddr(bool sapling, const std::function<void(json)>& cb) {
|
|
||||||
if (conn == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
json payload = {
|
|
||||||
{"jsonrpc", "1.0"},
|
|
||||||
{"id", "someid"},
|
|
||||||
{"method", "z_getnewaddress"},
|
|
||||||
{"params", { sapling ? "sapling" : "sprout" }},
|
|
||||||
};
|
|
||||||
|
|
||||||
conn->doRPCWithDefaultErrorHandling(payload, cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ZcashdRPC::createNewTaddr(const std::function<void(json)>& cb) {
|
|
||||||
if (conn == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
json payload = {
|
|
||||||
{"jsonrpc", "1.0"},
|
|
||||||
{"id", "someid"},
|
|
||||||
{"method", "getnewaddress"},
|
|
||||||
};
|
|
||||||
|
|
||||||
conn->doRPCWithDefaultErrorHandling(payload, cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ZcashdRPC::fetchZPrivKey(QString addr, const std::function<void(json)>& cb) {
|
|
||||||
if (conn == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
json payload = {
|
|
||||||
{"jsonrpc", "1.0"},
|
|
||||||
{"id", "someid"},
|
|
||||||
{"method", "z_exportkey"},
|
|
||||||
{"params", { addr.toStdString() }},
|
|
||||||
};
|
|
||||||
|
|
||||||
conn->doRPCWithDefaultErrorHandling(payload, cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ZcashdRPC::fetchTPrivKey(QString addr, const std::function<void(json)>& cb) {
|
|
||||||
if (conn == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
json payload = {
|
|
||||||
{"jsonrpc", "1.0"},
|
|
||||||
{"id", "someid"},
|
|
||||||
{"method", "dumpprivkey"},
|
|
||||||
{"params", { addr.toStdString() }},
|
|
||||||
};
|
|
||||||
|
|
||||||
conn->doRPCWithDefaultErrorHandling(payload, cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ZcashdRPC::importZPrivKey(QString addr, bool rescan, const std::function<void(json)>& cb) {
|
|
||||||
if (conn == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
json payload = {
|
|
||||||
{"jsonrpc", "1.0"},
|
|
||||||
{"id", "someid"},
|
|
||||||
{"method", "z_importkey"},
|
|
||||||
{"params", { addr.toStdString(), (rescan? "yes" : "no") }},
|
|
||||||
};
|
|
||||||
|
|
||||||
conn->doRPCWithDefaultErrorHandling(payload, cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void ZcashdRPC::importTPrivKey(QString addr, bool rescan, const std::function<void(json)>& cb) {
|
|
||||||
if (conn == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
json payload = {
|
|
||||||
{"jsonrpc", "1.0"},
|
|
||||||
{"id", "someid"},
|
|
||||||
{"method", "importprivkey"},
|
|
||||||
{"params", { addr.toStdString(), (rescan? "yes" : "no") }},
|
|
||||||
};
|
|
||||||
|
|
||||||
conn->doRPCWithDefaultErrorHandling(payload, cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ZcashdRPC::validateAddress(QString address, const std::function<void(json)>& cb) {
|
|
||||||
if (conn == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
QString method = Settings::isZAddress(address) ? "z_validateaddress" : "validateaddress";
|
|
||||||
|
|
||||||
json payload = {
|
|
||||||
{"jsonrpc", "1.0"},
|
|
||||||
{"id", "someid"},
|
|
||||||
{"method", method.toStdString() },
|
|
||||||
{"params", { address.toStdString() } },
|
|
||||||
};
|
|
||||||
|
|
||||||
conn->doRPCWithDefaultErrorHandling(payload, cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ZcashdRPC::fetchBalance(const std::function<void(json)>& cb) {
|
|
||||||
if (conn == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
json payload = {
|
|
||||||
{"jsonrpc", "1.0"},
|
|
||||||
{"id", "someid"},
|
|
||||||
{"method", "z_gettotalbalance"},
|
|
||||||
{"params", {0}} // Get Unconfirmed balance as well.
|
|
||||||
};
|
|
||||||
|
|
||||||
conn->doRPCWithDefaultErrorHandling(payload, cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ZcashdRPC::fetchTransactions(const std::function<void(json)>& cb) {
|
|
||||||
if (conn == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
json payload = {
|
|
||||||
{"jsonrpc", "1.0"},
|
|
||||||
{"id", "someid"},
|
|
||||||
{"method", "listtransactions"}
|
|
||||||
};
|
|
||||||
|
|
||||||
conn->doRPCWithDefaultErrorHandling(payload, cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ZcashdRPC::sendZTransaction(json params, const std::function<void(json)>& cb,
|
|
||||||
const std::function<void(QString)>& err) {
|
|
||||||
if (conn == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
json payload = {
|
|
||||||
{"jsonrpc", "1.0"},
|
|
||||||
{"id", "someid"},
|
|
||||||
{"method", "z_sendmany"},
|
|
||||||
{"params", params}
|
|
||||||
};
|
|
||||||
|
|
||||||
conn->doRPC(payload, cb, [=] (auto reply, auto parsed) {
|
|
||||||
if (!parsed.is_discarded() && !parsed["error"]["message"].is_null()) {
|
|
||||||
err(QString::fromStdString(parsed["error"]["message"]));
|
|
||||||
} else {
|
|
||||||
err(reply->errorString());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void ZcashdRPC::fetchInfo(const std::function<void(json)>& cb,
|
|
||||||
const std::function<void(QNetworkReply*, const json&)>& err) {
|
|
||||||
if (conn == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
json payload = {
|
|
||||||
{"jsonrpc", "1.0"},
|
|
||||||
{"id", "someid"},
|
|
||||||
{"method", "getinfo"}
|
|
||||||
};
|
|
||||||
|
|
||||||
conn->doRPC(payload, cb, err);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ZcashdRPC::fetchBlockchainInfo(const std::function<void(json)>& cb) {
|
|
||||||
if (conn == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
json payload = {
|
|
||||||
{"jsonrpc", "1.0"},
|
|
||||||
{"id", "someid"},
|
|
||||||
{"method", "getblockchaininfo"}
|
|
||||||
};
|
|
||||||
|
|
||||||
conn->doRPCIgnoreError(payload, cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ZcashdRPC::fetchNetSolOps(const std::function<void(qint64)> cb) {
|
|
||||||
if (conn == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
json payload = {
|
|
||||||
{"jsonrpc", "1.0"},
|
|
||||||
{"id", "someid"},
|
|
||||||
{"method", "getnetworksolps"}
|
|
||||||
};
|
|
||||||
|
|
||||||
conn->doRPCIgnoreError(payload, [=](const json& reply) {
|
|
||||||
qint64 solrate = reply.get<json::number_unsigned_t>();
|
|
||||||
cb(solrate);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void ZcashdRPC::fetchMigrationStatus(const std::function<void(json)>& cb) {
|
|
||||||
if (conn == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
json payload = {
|
|
||||||
{"jsonrpc", "1.0"},
|
|
||||||
{"id", "someid"},
|
|
||||||
{"method", "z_getmigrationstatus"},
|
|
||||||
};
|
|
||||||
|
|
||||||
conn->doRPCWithDefaultErrorHandling(payload, cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void ZcashdRPC::setMigrationStatus(bool enabled) {
|
|
||||||
if (conn == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
json payload = {
|
|
||||||
{"jsonrpc", "1.0"},
|
|
||||||
{"id", "someid"},
|
|
||||||
{"method", "z_setmigration"},
|
|
||||||
{"params", {enabled}}
|
|
||||||
};
|
|
||||||
|
|
||||||
conn->doRPCWithDefaultErrorHandling(payload, [=](json) {
|
|
||||||
// Ignore return value.
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method to get all the private keys for both z and t addresses. It will make 2 batch calls,
|
|
||||||
* combine the result, and call the callback with a single list containing both the t-addr and z-addr
|
|
||||||
* private keys
|
|
||||||
*/
|
|
||||||
void ZcashdRPC::fetchAllPrivKeys(const std::function<void(QList<QPair<QString, QString>>)> cb) {
|
|
||||||
if (conn == nullptr) {
|
|
||||||
// No connection, just return
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// A special function that will call the callback when two lists have been added
|
|
||||||
auto holder = new QPair<int, QList<QPair<QString, QString>>>();
|
|
||||||
holder->first = 0; // This is the number of times the callback has been called, initialized to 0
|
|
||||||
auto fnCombineTwoLists = [=] (QList<QPair<QString, QString>> list) {
|
|
||||||
// Increment the callback counter
|
|
||||||
holder->first++;
|
|
||||||
|
|
||||||
// Add all
|
|
||||||
std::copy(list.begin(), list.end(), std::back_inserter(holder->second));
|
|
||||||
|
|
||||||
// And if the caller has been called twice, do the parent callback with the
|
|
||||||
// collected list
|
|
||||||
if (holder->first == 2) {
|
|
||||||
// Sort so z addresses are on top
|
|
||||||
std::sort(holder->second.begin(), holder->second.end(),
|
|
||||||
[=] (auto a, auto b) { return a.first > b.first; });
|
|
||||||
|
|
||||||
cb(holder->second);
|
|
||||||
delete holder;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// A utility fn to do the batch calling
|
|
||||||
auto fnDoBatchGetPrivKeys = [=](json getAddressPayload, std::string privKeyDumpMethodName) {
|
|
||||||
conn->doRPCWithDefaultErrorHandling(getAddressPayload, [=] (json resp) {
|
|
||||||
QList<QString> addrs;
|
|
||||||
for (auto addr : resp.get<json::array_t>()) {
|
|
||||||
addrs.push_back(QString::fromStdString(addr.get<json::string_t>()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then, do a batch request to get all the private keys
|
|
||||||
conn->doBatchRPC<QString>(
|
|
||||||
addrs,
|
|
||||||
[=] (auto addr) {
|
|
||||||
json payload = {
|
|
||||||
{"jsonrpc", "1.0"},
|
|
||||||
{"id", "someid"},
|
|
||||||
{"method", privKeyDumpMethodName},
|
|
||||||
{"params", { addr.toStdString() }},
|
|
||||||
};
|
|
||||||
return payload;
|
|
||||||
},
|
|
||||||
[=] (QMap<QString, json>* privkeys) {
|
|
||||||
QList<QPair<QString, QString>> allTKeys;
|
|
||||||
for (QString addr: privkeys->keys()) {
|
|
||||||
allTKeys.push_back(
|
|
||||||
QPair<QString, QString>(
|
|
||||||
addr,
|
|
||||||
QString::fromStdString(privkeys->value(addr).get<json::string_t>())));
|
|
||||||
}
|
|
||||||
|
|
||||||
fnCombineTwoLists(allTKeys);
|
|
||||||
delete privkeys;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// First get all the t and z addresses.
|
|
||||||
json payloadT = {
|
|
||||||
{"jsonrpc", "1.0"},
|
|
||||||
{"id", "someid"},
|
|
||||||
{"method", "getaddressesbyaccount"},
|
|
||||||
{"params", {""} }
|
|
||||||
};
|
|
||||||
|
|
||||||
json payloadZ = {
|
|
||||||
{"jsonrpc", "1.0"},
|
|
||||||
{"id", "someid"},
|
|
||||||
{"method", "z_listaddresses"}
|
|
||||||
};
|
|
||||||
|
|
||||||
fnDoBatchGetPrivKeys(payloadT, "dumpprivkey");
|
|
||||||
fnDoBatchGetPrivKeys(payloadZ, "z_exportkey");
|
|
||||||
}
|
|
||||||
|
|
||||||
void ZcashdRPC::fetchOpStatus(const std::function<void(json)>& cb) {
|
|
||||||
if (conn == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Make an RPC to load pending operation statues
|
|
||||||
json payload = {
|
|
||||||
{"jsonrpc", "1.0"},
|
|
||||||
{"id", "someid"},
|
|
||||||
{"method", "z_getoperationstatus"},
|
|
||||||
};
|
|
||||||
|
|
||||||
conn->doRPCIgnoreError(payload, cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ZcashdRPC::fetchReceivedTTrans(QList<QString> txids, QList<TransactionItem> sentZTxs,
|
|
||||||
const std::function<void(QList<TransactionItem>)> txdataFn) {
|
|
||||||
if (conn == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Look up all the txids to get the confirmation count for them.
|
|
||||||
conn->doBatchRPC<QString>(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 j = txidList->value(sentTx.txid);
|
|
||||||
if (j.is_null())
|
|
||||||
continue;
|
|
||||||
auto error = j["confirmations"].is_null();
|
|
||||||
if (!error)
|
|
||||||
sentTx.confirmations = j["confirmations"].get<json::number_integer_t>();
|
|
||||||
}
|
|
||||||
|
|
||||||
txdataFn(newSentZTxs);
|
|
||||||
delete txidList;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Refresh received z txs by calling z_listreceivedbyaddress/gettransaction
|
|
||||||
void ZcashdRPC::fetchReceivedZTrans(QList<QString> zaddrs, const std::function<void(QString)> usedAddrFn,
|
|
||||||
const std::function<void(QList<TransactionItem>)> txdataFn) {
|
|
||||||
if (conn == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
|
|
||||||
// This method is complicated because z_listreceivedbyaddress only returns the txid, and
|
|
||||||
// we have to make a follow up call to gettransaction to get details of that transaction.
|
|
||||||
// Additionally, it has to be done in batches, because there are multiple z-Addresses,
|
|
||||||
// and each z-Addr can have multiple received txs.
|
|
||||||
|
|
||||||
// 1. For each z-Addr, get list of received txs
|
|
||||||
conn->doBatchRPC<QString>(zaddrs,
|
|
||||||
[=] (QString zaddr) {
|
|
||||||
json payload = {
|
|
||||||
{"jsonrpc", "1.0"},
|
|
||||||
{"id", "z_lrba"},
|
|
||||||
{"method", "z_listreceivedbyaddress"},
|
|
||||||
{"params", {zaddr.toStdString(), 0}} // Accept 0 conf as well.
|
|
||||||
};
|
|
||||||
|
|
||||||
return payload;
|
|
||||||
},
|
|
||||||
[=] (QMap<QString, json>* zaddrTxids) {
|
|
||||||
// Process all txids, removing duplicates. This can happen if the same address
|
|
||||||
// appears multiple times in a single tx's outputs.
|
|
||||||
QSet<QString> txids;
|
|
||||||
QMap<QString, QString> memos;
|
|
||||||
for (auto it = zaddrTxids->constBegin(); it != zaddrTxids->constEnd(); it++) {
|
|
||||||
auto zaddr = it.key();
|
|
||||||
for (auto& i : it.value().get<json::array_t>()) {
|
|
||||||
// Mark the address as used
|
|
||||||
usedAddrFn(zaddr);
|
|
||||||
|
|
||||||
// Filter out change txs
|
|
||||||
if (! i["change"].get<json::boolean_t>()) {
|
|
||||||
auto txid = QString::fromStdString(i["txid"].get<json::string_t>());
|
|
||||||
txids.insert(txid);
|
|
||||||
|
|
||||||
// Check for Memos
|
|
||||||
QString memoBytes = QString::fromStdString(i["memo"].get<json::string_t>());
|
|
||||||
if (!memoBytes.startsWith("f600")) {
|
|
||||||
QString memo(QByteArray::fromHex(
|
|
||||||
QByteArray::fromStdString(i["memo"].get<json::string_t>())));
|
|
||||||
if (!memo.trimmed().isEmpty())
|
|
||||||
memos[zaddr + txid] = memo;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. For all txids, go and get the details of that txid.
|
|
||||||
conn->doBatchRPC<QString>(txids.toList(),
|
|
||||||
[=] (QString txid) {
|
|
||||||
json payload = {
|
|
||||||
{"jsonrpc", "1.0"},
|
|
||||||
{"id", "gettx"},
|
|
||||||
{"method", "gettransaction"},
|
|
||||||
{"params", {txid.toStdString()}}
|
|
||||||
};
|
|
||||||
|
|
||||||
return payload;
|
|
||||||
},
|
|
||||||
[=] (QMap<QString, json>* txidDetails) {
|
|
||||||
QList<TransactionItem> txdata;
|
|
||||||
|
|
||||||
// Combine them both together. For every zAddr's txid, get the amount, fee, confirmations and time
|
|
||||||
for (auto it = zaddrTxids->constBegin(); it != zaddrTxids->constEnd(); it++) {
|
|
||||||
for (auto& i : it.value().get<json::array_t>()) {
|
|
||||||
// Filter out change txs
|
|
||||||
if (i["change"].get<json::boolean_t>())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
auto zaddr = it.key();
|
|
||||||
auto txid = QString::fromStdString(i["txid"].get<json::string_t>());
|
|
||||||
|
|
||||||
// Lookup txid in the map
|
|
||||||
auto txidInfo = txidDetails->value(txid);
|
|
||||||
|
|
||||||
qint64 timestamp;
|
|
||||||
if (txidInfo.find("time") != txidInfo.end()) {
|
|
||||||
timestamp = txidInfo["time"].get<json::number_unsigned_t>();
|
|
||||||
} else {
|
|
||||||
timestamp = txidInfo["blocktime"].get<json::number_unsigned_t>();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto amount = i["amount"].get<json::number_float_t>();
|
|
||||||
auto confirmations = static_cast<long>(txidInfo["confirmations"].get<json::number_integer_t>());
|
|
||||||
|
|
||||||
TransactionItem tx{ QString("receive"), timestamp, zaddr, txid, amount,
|
|
||||||
confirmations, "", memos.value(zaddr + txid, "") };
|
|
||||||
txdata.push_front(tx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
txdataFn(txdata);
|
|
||||||
|
|
||||||
// Cleanup both responses;
|
|
||||||
delete zaddrTxids;
|
|
||||||
delete txidDetails;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -62,7 +62,7 @@ SOURCES += \
|
|||||||
src/viewalladdresses.cpp \
|
src/viewalladdresses.cpp \
|
||||||
src/datamodel.cpp \
|
src/datamodel.cpp \
|
||||||
src/controller.cpp \
|
src/controller.cpp \
|
||||||
src/zcashdrpc.cpp
|
src/liteinterface.cpp
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
src/mainwindow.h \
|
src/mainwindow.h \
|
||||||
@@ -91,7 +91,7 @@ HEADERS += \
|
|||||||
src/viewalladdresses.h \
|
src/viewalladdresses.h \
|
||||||
src/datamodel.h \
|
src/datamodel.h \
|
||||||
src/controller.h \
|
src/controller.h \
|
||||||
src/zcashdrpc.h \
|
src/liteinterface.h \
|
||||||
lib/zecwalletlitelib.h
|
lib/zecwalletlitelib.h
|
||||||
|
|
||||||
FORMS += \
|
FORMS += \
|
||||||
|
|||||||
Reference in New Issue
Block a user