Hush full node GUI wallet
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

454 lines
16 KiB

6 years ago
#include "connection.h"
#include "mainwindow.h"
#include "settings.h"
#include "ui_connection.h"
#include "rpc.h"
#include "precompiled.h"
using json = nlohmann::json;
6 years ago
ConnectionLoader::ConnectionLoader(MainWindow* main, RPC* rpc) {
6 years ago
this->main = main;
this->rpc = rpc;
6 years ago
d = new QDialog(main);
6 years ago
connD = new Ui_ConnectionDialog();
connD->setupUi(d);
// Center on screen
QRect screenGeometry = QApplication::desktop()->screenGeometry(d);
int x = (screenGeometry.width() - d->width()) / 2;
int y = (screenGeometry.height() - d->height()) / 2;
d->move(x, y);
6 years ago
}
ConnectionLoader::~ConnectionLoader() {
delete d;
delete connD;
}
void ConnectionLoader::loadConnection() {
// Load from settings if it is a manual connection.
if (Settings::getInstance()->getIsManualConnection()) {
doManualConnect();
} else {
doAutoConnect();
}
}
void ConnectionLoader::doAutoConnect() {
6 years ago
// Priority 1: Try to connect to detect zcash.conf and connect to it.
auto config = autoDetectZcashConf();
6 years ago
if (config.get() != nullptr) {
auto connection = makeConnection(config);
refreshZcashdState(connection);
6 years ago
return;
} else {
// zcash.conf was not found, so create one
createZcashConf();
}
}
6 years ago
/**
* This will create a new zcash.conf, download zcash parameters.
*/
void ConnectionLoader::createZcashConf() {
// Create zcash.conf
{
auto confLocation = zcashConfWritableLocation();
qDebug() << "Creating file " << confLocation;
QFileInfo fi(confLocation);
QDir().mkdir(fi.dir().absolutePath());
QFile file(confLocation);
6 years ago
if (!file.open(QIODevice::ReadWrite | QIODevice::Truncate)) {
qDebug() << "Could not create zcash.conf, returning";
return;
}
QTextStream out(&file);
out << "server=1\n";
out << "rpcuser=zec-qt-wallet\n";
out << "rpcpassword=" % QString::number(std::rand()) << "\n";
file.close();
}
6 years ago
// Fetch params.
{
QFileInfo fi(Settings::getInstance()->getExecName());
auto fetchParamsProgram = fi.dir().filePath("zcash-fetch-params");
QProcess* p = new QProcess(main);
QObject::connect(p, &QProcess::readyReadStandardOutput, [=] () {
qDebug() << "stdout:" << p->readAllStandardOutput();
});
QObject::connect(p, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
[=](int exitCode, QProcess::ExitStatus exitStatus) {
qDebug() << "finished with code " << exitCode;
p->deleteLater();
});
p->start(fetchParamsProgram);
// stdout: "Zcash - fetch-params.sh\n\nThis script will fetch the Zcash zkSNARK parameters and verify their\nintegrity with sha256sum.\n\nIf they already exist locally, it will exit now and do nothing else.\nThe parameters are currently just under 911MB in size, so plan accordingly\nfor your bandwidth constraints. If the files are already present and\nhave the correct sha256sum, no networking is used.\n\nCreating params directory. For details about this directory, see:\n/home/adityapk/.zcash-params/README\n\n\nRetrieving (wget): https://z.cash/downloads/sprout-proving.key\n"
// stdout: "Download successful!\n"
// stdout: "/home/adityapk/.zcash-params/sprout-proving.key.dl: OK\n"
// stdout: "renamed '/home/adityapk/.zcash-params/sprout-proving.key.dl' -> '/home/adityapk/.zcash-params/sprout-proving.key'\n"
// stdout: "\nRetrieving (wget): https://z.cash/downloads/sprout-verifying.key\n"
// stdout: "Download successful!\n"
// stdout: "/home/adityapk/.zcash-params/sprout-verifying.key.dl: OK\n"
// stdout: "renamed '/home/adityapk/.zcash-params/sprout-verifying.key.dl' -> '/home/adityapk/.zcash-params/sprout-verifying.key'\n"
// stdout: "\nRetrieving (wget): https://z.cash/downloads/sapling-spend.params\n"
// stdout: "Download successful!\n"
// stdout: "/home/adityapk/.zcash-params/sapling-spend.params.dl: OK\n"
// stdout: "renamed '/home/adityapk/.zcash-params/sapling-spend.params.dl' -> '/home/adityapk/.zcash-params/sapling-spend.params'\n"
// stdout: "\nRetrieving (wget): https://z.cash/downloads/sapling-output.params\n"
// stdout: "Download successful!\n"
// stdout: "/home/adityapk/.zcash-params/sapling-output.params.dl: OK\n"
// stdout: "renamed '/home/adityapk/.zcash-params/sapling-output.params.dl' -> '/home/adityapk/.zcash-params/sapling-output.params'\n"
// stdout: "\nRetrieving (wget): https://z.cash/downloads/sprout-groth16.params\n"
// stdout: "Download successful!\n"
// stdout: "/home/adityapk/.zcash-params/sprout-groth16.params.dl: OK\n"
// stdout: "renamed '/home/adityapk/.zcash-params/sprout-groth16.params.dl' -> '/home/adityapk/.zcash-params/sprout-groth16.params'\n"
// finished with code 0
}
6 years ago
{
startEmbeddedZcashd();
auto config = autoDetectZcashConf();
if (config.get() != nullptr) {
auto connection = makeConnection(config);
refreshZcashdState(connection);
return;
} else {
qDebug() << "Coulnd't get embedded startup zcashd";
}
}
}
bool ConnectionLoader::startEmbeddedZcashd() {
if (ezcashd != nullptr) {
if (ezcashd->state() == QProcess::NotRunning) {
qDebug() << "Process started and then crashed";
return false;
} else {
return true;
}
6 years ago
}
// Finally, start zcashd
qDebug() << "Starting zcashd";
QFileInfo fi(Settings::getInstance()->getExecName());
#ifdef Q_OS_LINUX
auto zcashdProgram = "zcashd";
#elif defined(Q_OS_DARWIN)
auto zcashdProgram = "zcashd";
#else
auto zcashdProgram = "zcashd.exe";
#endif
qDebug() << zcashdProgram << QFile(zcashdProgram).exists();
6 years ago
ezcashd = new QProcess(main);
ezcashd->setWorkingDirectory(fi.dir().absolutePath());
QObject::connect(ezcashd, &QProcess::started, [=] () {
qDebug() << "zcashd started";
6 years ago
Settings::getInstance()->setEmbeddedZcashdRunning(true);
});
QObject::connect(ezcashd, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
6 years ago
[=](int exitCode, QProcess::ExitStatus exitStatus) {
qDebug() << "zcashd finished with code " << exitCode << "," << exitStatus;
6 years ago
});
QObject::connect(ezcashd, &QProcess::errorOccurred, [&] (auto error) mutable {
qDebug() << "Couldn't start zcashd: " << error;
6 years ago
});
ezcashd->start(zcashdProgram);
6 years ago
return true;
}
void ConnectionLoader::doManualConnect() {
auto config = loadFromSettings();
if (config.get() == nullptr) {
d->show();
// Nothing configured, show an error
QString explanation = QString()
% "A manual connection was requested, but the settings are not configured.\n\n"
% "Please set the host/port and user/password in the File->Settings menu.";
showError(explanation);
doRPCSetConnection(nullptr);
return;
6 years ago
}
auto connection = makeConnection(config);
refreshZcashdState(connection);
}
6 years ago
void ConnectionLoader::doRPCSetConnection(Connection* conn) {
if (ezcashd != nullptr) {
rpc->setEZcashd(ezcashd);
}
6 years ago
rpc->setConnection(conn);
delete this;
}
Connection* ConnectionLoader::makeConnection(std::shared_ptr<ConnectionConfig> config) {
6 years ago
QNetworkAccessManager* client = new QNetworkAccessManager(main);
QUrl myurl;
myurl.setScheme("http");
myurl.setHost(config.get()->host);
myurl.setPort(config.get()->port.toInt());
6 years ago
QNetworkRequest* request = new QNetworkRequest();
request->setUrl(myurl);
request->setHeader(QNetworkRequest::ContentTypeHeader, "text/plain");
QString userpass = config.get()->rpcuser % ":" % config.get()->rpcpassword;
QString headerData = "Basic " + userpass.toLocal8Bit().toBase64();
request->setRawHeader("Authorization", headerData.toLocal8Bit());
return new Connection(main, client, request, config);
}
6 years ago
void ConnectionLoader::refreshZcashdState(Connection* connection) {
6 years ago
json payload = {
{"jsonrpc", "1.0"},
{"id", "someid"},
{"method", "getinfo"}
};
connection->doRPC(payload,
[=] (auto) {
// Success, hide the dialog if it was shown.
d->hide();
6 years ago
this->doRPCSetConnection(connection);
6 years ago
},
[=] (auto reply, auto res) {
d->show();
auto err = reply->error();
// Failed, see what it is.
//qDebug() << err << ":" << QString::fromStdString(res.dump());
6 years ago
if (err == QNetworkReply::NetworkError::ConnectionRefusedError) {
// Start embedded zcasd
this->showInformation("Starting Embedded zcashd");
if (this->startEmbeddedZcashd()) {
// Refresh after one second
QTimer::singleShot(1000, [=]() { this->refreshZcashdState(connection); });
} else {
// Errored out, show error and exit
QString explanation = "Couldn't start the embedded zcashd. The process returned:\n\n" % ezcashd->errorString();
6 years ago
this->showError(explanation);
}
} else if (err == QNetworkReply::NetworkError::AuthenticationRequiredError) {
QString explanation = QString()
% "Authentication failed. The username / password you specified was "
% "not accepted by zcashd. Try changing it in the Edit->Settings menu";
6 years ago
this->showError(explanation);
} else if (err == QNetworkReply::NetworkError::InternalServerError && !res.is_discarded()) {
// The server is loading, so just poll until it succeeds
QString status = QString::fromStdString(res["error"]["message"]);
showInformation("Your zcashd is starting up. Please wait.", status);
// Refresh after one second
QTimer::singleShot(1000, [=]() { this->refreshZcashdState(connection); });
}
6 years ago
}
);
}
void ConnectionLoader::showInformation(QString info, QString detail) {
6 years ago
connD->status->setText(info);
connD->statusDetail->setText(detail);
6 years ago
}
/**
* Show error will close the loading dialog and show an error.
*/
void ConnectionLoader::showError(QString explanation) {
d->close();
QMessageBox::critical(main, "Error", explanation, QMessageBox::Ok);
}
QString ConnectionLoader::locateZcashConfFile() {
6 years ago
#ifdef Q_OS_LINUX
auto confLocation = QStandardPaths::locate(QStandardPaths::HomeLocation, ".zcash/zcash.conf");
#elif defined(Q_OS_DARWIN)
auto confLocation = QStandardPaths::locate(QStandardPaths::HomeLocation, "/Library/Application Support/Zcash/zcash.conf");
#else
auto confLocation = QStandardPaths::locate(QStandardPaths::AppDataLocation, "../../Zcash/zcash.conf");
#endif
return QDir::cleanPath(confLocation);
}
6 years ago
QString ConnectionLoader::zcashConfWritableLocation() {
#ifdef Q_OS_LINUX
auto confLocation = QDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)).filePath(".zcash/zcash.conf");
#elif defined(Q_OS_DARWIN)
auto confLocation = QDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)).filePath("/Library/Application Support/Zcash/zcash.conf");
#else
auto confLocation = QDir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)).filePath("../../Zcash/zcash.conf");
#endif
return confLocation;
}
/**
* Try to automatically detect a zcash.conf file in the correct location and load parameters
*/
std::shared_ptr<ConnectionConfig> ConnectionLoader::autoDetectZcashConf() {
auto confLocation = locateZcashConfFile();
6 years ago
if (confLocation.isNull()) {
// No zcash file, just return with nothing
return nullptr;
}
QFile file(confLocation);
if (!file.open(QIODevice::ReadOnly)) {
qDebug() << file.errorString();
return nullptr;
}
QTextStream in(&file);
auto zcashconf = new ConnectionConfig();
zcashconf->host = "127.0.0.1";
zcashconf->connType = ConnectionType::DetectedConfExternalZcashD;
zcashconf->usingZcashConf = true;
Settings::getInstance()->setUsingZcashConf(confLocation);
6 years ago
while (!in.atEnd()) {
QString line = in.readLine();
auto s = line.indexOf("=");
QString name = line.left(s).trimmed().toLower();
QString value = line.right(line.length() - s - 1).trimmed();
if (name == "rpcuser") {
zcashconf->rpcuser = value;
}
if (name == "rpcpassword") {
zcashconf->rpcpassword = value;
}
if (name == "rpcport") {
zcashconf->port = value;
}
if (name == "testnet" &&
value == "1" &&
zcashconf->port.isEmpty()) {
zcashconf->port = "18232";
}
}
// If rpcport is not in the file, and it was not set by the testnet=1 flag, then go to default
if (zcashconf->port.isEmpty()) zcashconf->port = "8232";
file.close();
return std::shared_ptr<ConnectionConfig>(zcashconf);
6 years ago
}
/**
* Load connection settings from the UI, which indicates an unknown, external zcashd
*/
std::shared_ptr<ConnectionConfig> ConnectionLoader::loadFromSettings() {
6 years ago
// Load from the QT Settings.
QSettings s;
auto host = s.value("connection/host").toString();
auto port = s.value("connection/port").toString();
auto username = s.value("connection/rpcuser").toString();
auto password = s.value("connection/rpcpassword").toString();
if (username.isEmpty() || password.isEmpty())
return nullptr;
auto uiConfig = new ConnectionConfig{ host, port, username, password, false, ConnectionType::UISettingsZCashD };
6 years ago
return std::shared_ptr<ConnectionConfig>(uiConfig);
6 years ago
}
6 years ago
/***********************************************************************************
* Connection Class
************************************************************************************/
Connection::Connection(MainWindow* m, QNetworkAccessManager* c, QNetworkRequest* r,
std::shared_ptr<ConnectionConfig> conf) {
6 years ago
this->restclient = c;
this->request = r;
this->config = conf;
this->main = m;
6 years ago
}
Connection::~Connection() {
delete restclient;
delete request;
}
void Connection::doRPC(const json& payload, const std::function<void(json)>& cb,
const std::function<void(QNetworkReply*, const json&)>& ne) {
6 years ago
QNetworkReply *reply = restclient->post(*request, QByteArray::fromStdString(payload.dump()));
QObject::connect(reply, &QNetworkReply::finished, [=] {
reply->deleteLater();
if (reply->error() != QNetworkReply::NoError) {
auto parsed = json::parse(reply->readAll(), nullptr, false);
ne(reply, parsed);
6 years ago
return;
}
auto parsed = json::parse(reply->readAll(), nullptr, false);
if (parsed.is_discarded()) {
ne(reply, "Unknown error");
6 years ago
}
cb(parsed["result"]);
});
}
void Connection::doRPCWithDefaultErrorHandling(const json& payload, const std::function<void(json)>& cb) {
doRPC(payload, cb, [=] (auto reply, auto parsed) {
if (!parsed.is_discarded() && !parsed["error"]["message"].is_null()) {
6 years ago
this->showTxError(QString::fromStdString(parsed["error"]["message"]));
} else {
6 years ago
this->showTxError(reply->errorString());
}
});
}
void Connection::doRPCIgnoreError(const json& payload, const std::function<void(json)>& cb) {
doRPC(payload, cb, [=] (auto, auto) {
// Ignored error handling
});
}
void Connection::showTxError(const QString& error) {
if (error.isNull()) return;
6 years ago
QMessageBox::critical(main, "Transaction Error", "There was an error sending the transaction. The error was: \n\n"
+ error, QMessageBox::StandardButton::Ok);
6 years ago
}