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.

616 lines
21 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);
QPixmap logo(":/img/res/logobig.gif");
connD->topIcon->setBasePixmap(logo.scaled(256, 256, Qt::KeepAspectRatio, Qt::SmoothTransformation));
6 years ago
}
ConnectionLoader::~ConnectionLoader() {
6 years ago
delete d;
delete connD;
}
void ConnectionLoader::loadConnection() {
QTimer::singleShot(1, [=]() { this->doAutoConnect(); });
d->exec();
}
void ConnectionLoader::doAutoConnect(bool tryEzcashdStart) {
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);
6 years ago
refreshZcashdState(connection, [=] () {
// Refused connection. So try and start embedded zcashd
if (Settings::getInstance()->useEmbedded()) {
if (tryEzcashdStart) {
this->showInformation("Starting embedded zcashd");
if (this->startEmbeddedZcashd()) {
// Embedded zcashd started up. Wait a second and then refresh the connection
QTimer::singleShot(1000, [=]() { doAutoConnect(); } );
} else {
// Something is wrong. This is happenening intermittently on Mac platforms.
// - We tried to start zcashd
// - QProcess started
// - QProcess ended, but the embedded zcashd is still in the background.
// 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
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.
QString explanation = QString() % "Couldn't start the embedded zcashd.\n\n" %
"Please try restarting.\n\nIfIf 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 ? "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!
QString explanation = QString() % "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();
}
}
}
6 years ago
QString randomPassword() {
static const char alphanum[] =
"0123456789"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz";
const int passwordLength = 10;
char* s = new char[passwordLength + 1];
for (int i = 0; i < passwordLength; ++i) {
s[i] = alphanum[rand() % (sizeof(alphanum) - 1)];
}
s[passwordLength] = 0;
return QString::fromStdString(s);
}
/**
* This will create a new zcash.conf, download Zcash parameters.
*/
void ConnectionLoader::createZcashConf() {
// Fetch params. After params are fetched, create the zcash.conf file and
// try loading the connection again
downloadParams([=] () {
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 << "addnode=mainnet.z.cash\n";
out << "rpcuser=zec-qt-wallet\n";
out << "rpcpassword=" % randomPassword() << "\n";
file.close();
6 years ago
this->doAutoConnect();
});
6 years ago
}
6 years ago
6 years ago
void ConnectionLoader::downloadParams(std::function<void(void)> cb) {
// Add all the files to the download queue
downloadQueue = new QQueue<QUrl>();
client = new QNetworkAccessManager(main);
downloadQueue->enqueue(QUrl("https://z.cash/downloads/sapling-output.params"));
downloadQueue->enqueue(QUrl("https://z.cash/downloads/sapling-spend.params"));
downloadQueue->enqueue(QUrl("https://z.cash/downloads/sprout-proving.key"));
downloadQueue->enqueue(QUrl("https://z.cash/downloads/sprout-verifying.key"));
downloadQueue->enqueue(QUrl("https://z.cash/downloads/sprout-groth16.params"));
6 years ago
doNextDownload(cb);
6 years ago
}
void ConnectionLoader::doNextDownload(std::function<void(void)> cb) {
auto fnSaveFileName = [&] (QUrl url) {
QString path = url.path();
QString basename = QFileInfo(path).fileName();
return basename;
};
if (downloadQueue->isEmpty()) {
delete downloadQueue;
client->deleteLater();
this->showInformation("All Downloads Finished Successfully!");
cb();
return;
}
QUrl url = downloadQueue->dequeue();
int filesRemaining = downloadQueue->size();
QString filename = fnSaveFileName(url);
QString paramsDir = zcashParamsDir();
6 years ago
if (QFile(QDir(paramsDir).filePath(filename)).exists()) {
qDebug() << filename << " already exists, skipping ";
doNextDownload(cb);
return;
}
6 years ago
// The downloaded file is written to a new name, and then renamed when the operation completes.
currentOutput = new QFile(QDir(paramsDir).filePath(filename + ".part"));
6 years ago
if (!currentOutput->open(QIODevice::WriteOnly)) {
this->showError("Couldn't download params. Please check the help site for more info.");
}
qDebug() << "Downloading " << url << " to " << filename;
QNetworkRequest request(url);
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
currentDownload = client->get(request);
downloadTime.start();
// Download Progress
QObject::connect(currentDownload, &QNetworkReply::downloadProgress, [=] (auto done, auto total) {
// calculate the download speed
double speed = done * 1000.0 / downloadTime.elapsed();
QString unit;
if (speed < 1024) {
unit = "bytes/sec";
} else if (speed < 1024*1024) {
speed /= 1024;
unit = "kB/s";
6 years ago
} else {
6 years ago
speed /= 1024*1024;
unit = "MB/s";
6 years ago
}
6 years ago
this->showInformation(
"Downloading " % filename % (filesRemaining > 1 ? " ( +" % QString::number(filesRemaining) % " more remaining )" : QString("")),
QString::number(done/1024/1024, 'f', 0) % "MB of " % QString::number(total/1024/1024, 'f', 0) + "MB at " % QString::number(speed, 'f', 2) % unit);
});
// Download Finished
QObject::connect(currentDownload, &QNetworkReply::finished, [=] () {
6 years ago
// Rename file
currentOutput->rename(QDir(paramsDir).filePath(filename));
6 years ago
currentOutput->close();
currentDownload->deleteLater();
currentOutput->deleteLater();
if (currentDownload->error()) {
this->showError("Downloading " + filename + " failed/ Please check the help site for more info");
} else {
doNextDownload(cb);
}
});
// Download new data available.
QObject::connect(currentDownload, &QNetworkReply::readyRead, [=] () {
currentOutput->write(currentDownload->readAll());
});
6 years ago
}
bool ConnectionLoader::startEmbeddedZcashd() {
if (!Settings::getInstance()->useEmbedded())
return false;
// Static because it needs to survive even after this method returns.
static QString processStdErrOutput;
if (ezcashd != nullptr) {
if (ezcashd->state() == QProcess::NotRunning) {
qDebug() << "Process started and then crashed";
if (!processStdErrOutput.isEmpty()) {
QMessageBox::critical(main, "zcashd error", "zcashd said: " + processStdErrOutput,
QMessageBox::Ok);
}
return false;
} else {
return true;
}
6 years ago
}
// Finally, start zcashd
QFileInfo fi(Settings::getInstance()->getExecName());
#ifdef Q_OS_LINUX
auto zcashdProgram = fi.dir().absoluteFilePath("zqw-zcashd");
if (!QFile(zcashdProgram).exists()) {
zcashdProgram = fi.dir().absoluteFilePath("zcashd");
}
#elif defined(Q_OS_DARWIN)
auto zcashdProgram = fi.dir().absoluteFilePath("zcashd");
#else
auto zcashdProgram = fi.dir().absoluteFilePath("zcashd.exe");
#endif
6 years ago
if (!QFile(zcashdProgram).exists()) {
qDebug() << "Can't find zcashd at " << zcashdProgram;
6 years ago
return false;
}
6 years ago
ezcashd = new QProcess(main);
QObject::connect(ezcashd, &QProcess::started, [=] () {
6 years ago
//qDebug() << "zcashd started";
6 years ago
});
QObject::connect(ezcashd, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
6 years ago
[=](int, QProcess::ExitStatus) {
//qDebug() << "zcashd finished with code " << exitCode << "," << exitStatus;
6 years ago
});
6 years ago
QObject::connect(ezcashd, &QProcess::errorOccurred, [&] (auto) {
//qDebug() << "Couldn't start zcashd: " << error;
6 years ago
});
QObject::connect(ezcashd, &QProcess::readyReadStandardError, [=]() {
auto output = ezcashd->readAllStandardError();
qDebug() << "zcashd stderr:" << output;
processStdErrOutput.append(output);
});
#ifdef Q_OS_LINUX
ezcashd->start(zcashdProgram);
6 years ago
#elif defined(Q_OS_DARWIN)
ezcashd->start(zcashdProgram);
#else
ezcashd->setWorkingDirectory(fi.dir().absolutePath());
ezcashd->start("zcashd.exe");
#endif // Q_OS_LINUX
6 years ago
return true;
}
void ConnectionLoader::doManualConnect() {
auto config = loadFromSettings();
6 years ago
if (!config) {
// 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 Edit->Settings menu.";
showError(explanation);
doRPCSetConnection(nullptr);
return;
6 years ago
}
auto connection = makeConnection(config);
refreshZcashdState(connection, [=] () {
QString explanation = QString()
% "Could not connect to zcashd configured in settings.\n\n"
% "Please set the host/port and user/password in the Edit->Settings menu.";
showError(explanation);
doRPCSetConnection(nullptr);
return;
});
}
6 years ago
void ConnectionLoader::doRPCSetConnection(Connection* conn) {
rpc->setEZcashd(ezcashd);
6 years ago
rpc->setConnection(conn);
d->accept();
6 years ago
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, std::function<void(void)> refused) {
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) {
// Failed, see what it is.
auto err = reply->error();
//qDebug() << err << ":" << QString::fromStdString(res.dump());
6 years ago
if (err == QNetworkReply::NetworkError::ConnectionRefusedError) {
refused();
} 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"]);
{
static int dots = 0;
status = status.left(status.length() - 3) + QString(".").repeated(dots);
dots++;
if (dots > 3)
dots = 0;
}
this->showInformation("Your zcashd is starting up. Please wait.", status);
// Refresh after one second
QTimer::singleShot(1000, [=]() { this->refreshZcashdState(connection, refused); });
}
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) {
rpc->setEZcashd(nullptr);
rpc->noConnection();
QMessageBox::critical(main, "Connection Error", explanation, QMessageBox::Ok);
d->close();
}
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");
6 years ago
#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;
}
6 years ago
QString ConnectionLoader::zcashParamsDir() {
#ifdef Q_OS_LINUX
auto paramsLocation = QDir(QDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)).filePath(".zcash-params"));
#elif defined(Q_OS_DARWIN)
auto paramsLocation = QDir(QDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)).filePath("Library/Application Support/ZcashParams"));
6 years ago
#else
auto paramsLocation = QDir(QDir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)).filePath("../../ZcashParams"));
#endif
if (!paramsLocation.exists()) {
QDir().mkpath(paramsLocation.absolutePath());
}
return paramsLocation.absolutePath();
}
/**
* 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
6 years ago
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;
zcashconf->zcashDir = QFileInfo(confLocation).absoluteDir().absolutePath();
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) {
if (shutdownInProgress) {
6 years ago
// Ignoring RPC because shutdown in progress
return;
}
6 years ago
QNetworkReply *reply = restclient->post(*request, QByteArray::fromStdString(payload.dump()));
QObject::connect(reply, &QNetworkReply::finished, [=] {
reply->deleteLater();
if (shutdownInProgress) {
6 years ago
// Ignoring callback because shutdown in progress
return;
}
6 years ago
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
}
/**
* Prevent all future calls from going through
*/
void Connection::shutdown() {
shutdownInProgress = true;
6 years ago
}