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.
1115 lines
39 KiB
1115 lines
39 KiB
// Copyright 2019-2023 The Hush developers
|
|
// Released under the GPLv3
|
|
#include "connection.h"
|
|
#include "connection.h"
|
|
#include "mainwindow.h"
|
|
#include "settings.h"
|
|
#include "ui_connection.h"
|
|
#include "ui_createhushconfdialog.h"
|
|
#include "rpc.h"
|
|
#include "precompiled.h"
|
|
#include "version.h"
|
|
|
|
extern bool isHSC;
|
|
extern std::string HSC_name;
|
|
extern std::string HSC_ac_name;
|
|
extern std::string HSC_port;
|
|
extern std::string App_title;
|
|
extern std::string HSC_website;
|
|
extern std::string HSC_param;
|
|
extern std::string HSC_app_name;
|
|
extern std::string HSC_website;
|
|
|
|
ConnectionLoader::ConnectionLoader(MainWindow *main, RPC *rpc)
|
|
{
|
|
qDebug() << __func__;
|
|
this->main = main;
|
|
this->rpc = rpc;
|
|
|
|
d = new QDialog(main);
|
|
d->setWindowFlags(d->windowFlags() & ~(Qt::WindowCloseButtonHint | Qt::WindowContextHelpButtonHint));
|
|
connD = new Ui_ConnectionDialog();
|
|
connD->setupUi(d);
|
|
if (isHSC)
|
|
{
|
|
d->setWindowTitle(QString::fromStdString(App_title));
|
|
}
|
|
QMovie *movie1 = new QMovie(":/img/silentdragon-animated-startup-dark.gif");
|
|
;
|
|
auto theme = Settings::getInstance()->get_theme_name();
|
|
auto size = QSize(512, 512);
|
|
|
|
movie1->setScaledSize(size);
|
|
connD->topIcon->setMovie(movie1);
|
|
movie1->start();
|
|
|
|
main->logger->write("set animation");
|
|
qDebug() << "set animation";
|
|
}
|
|
|
|
ConnectionLoader::~ConnectionLoader()
|
|
{
|
|
delete d;
|
|
delete connD;
|
|
main->logger->write("ConnectionLoader done");
|
|
qDebug() << "connection loader done";
|
|
}
|
|
|
|
void ConnectionLoader::loadConnection()
|
|
{
|
|
qDebug() << __func__;
|
|
QTimer::singleShot(1, [=]()
|
|
{ this->doAutoConnect(); });
|
|
if (!Settings::getInstance()->isHeadless())
|
|
d->exec();
|
|
}
|
|
|
|
void ConnectionLoader::doAutoConnect(bool tryEhushdStart)
|
|
{
|
|
qDebug() << __func__;
|
|
// Priority 1: Ensure all params are present.
|
|
if (!verifyParams())
|
|
{
|
|
qDebug() << "Cannot find sapling params!";
|
|
return;
|
|
}
|
|
|
|
// Priority 2: Try to connect to detect HUSH3.conf and connect to it.
|
|
auto config = autoDetectHushConf();
|
|
main->logger->write(QObject::tr("Attempting autoconnect"));
|
|
|
|
if (config.get() != nullptr)
|
|
{
|
|
auto connection = makeConnection(config);
|
|
|
|
refreshHushdState(connection, [=]()
|
|
{
|
|
// Refused connection. So try and start embedded hushd
|
|
if (Settings::getInstance()->useEmbedded()) {
|
|
if (tryEhushdStart) {
|
|
if(isHSC) {
|
|
|
|
std::string start_daemon = "Starting embedded" + HSC_name + "d";
|
|
this->showInformation(QObject::tr(start_daemon.c_str()));
|
|
|
|
} else {
|
|
this->showInformation(QObject::tr("Starting embedded hushd"));
|
|
}
|
|
if (this->startEmbeddedHushd()) {
|
|
// Embedded hushd started up. Wait a second and then refresh the connection
|
|
main->logger->write("Embedded hushd started up, trying autoconnect in 1 sec");
|
|
QTimer::singleShot(1000, [=]() { doAutoConnect(); } );
|
|
} else {
|
|
if (config->hushDaemon) {
|
|
// hushd is configured to run as a daemon, so we must wait for a few seconds
|
|
// to let it start up.
|
|
main->logger->write("hushd is daemon=1. Waiting for it to start up");
|
|
this->showInformation(QObject::tr("hushd is set to run as daemon"), QObject::tr("Waiting for hushd"));
|
|
QTimer::singleShot(5000, [=]() { doAutoConnect(/* don't attempt to start ehushd */ 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 hushd!");
|
|
QTimer::singleShot(2000, [=]() { doAutoConnect(/* don't attempt to start ehushd */ false); });
|
|
}
|
|
}
|
|
} else {
|
|
// We tried to start ehushd previously, and it didn't work. So, show the error.
|
|
main->logger->write("Couldn't start embedded hushd for unknown reason");
|
|
QString explanation;
|
|
if (config->hushDaemon) {
|
|
explanation = QString() % QObject::tr("You have hushd set to start as a daemon, which can cause problems "
|
|
"with SilentDragon\n\n."
|
|
"Please remove the following line from your HUSH3.conf and restart SilentDragon\n"
|
|
"daemon=1");
|
|
if (isHSC) {
|
|
|
|
std::string daemon_error = "You have " + HSC_name + "d set to start as a daemon, which can cause problems "
|
|
"with " + App_title + "\n\n."
|
|
"Please remove the following line from your " + HSC_ac_name + ".conf and restart " + App_title + "\n"
|
|
"daemon=1";
|
|
|
|
explanation = QString() % QObject::tr(daemon_error.c_str());
|
|
}
|
|
} else {
|
|
explanation = QString() % QObject::tr("Couldn't start the embedded hushd.\n\n"
|
|
"Please try restarting.\n\nIf you previously started hushd with custom arguments, you might need to reset HUSH3.conf.\n\n"
|
|
"If all else fails, please run hushd manually.") %
|
|
(ehushd ? QObject::tr("The process returned") + ":\n\n" % ehushd->errorString() : QString(""));
|
|
if(isHSC) {
|
|
|
|
std::string daemon_error = "Couldn't start the embedded " + HSC_name + "d.\n\n"
|
|
"Please try restarting.\n\nIf you previously started hushd with custom arguments, you might need to reset " + HSC_ac_name + ".conf.\n\n"
|
|
"If all else fails, please run " + HSC_name + "d manually.";
|
|
|
|
explanation = QString() % QObject::tr(daemon_error.c_str()) %
|
|
(ehushd ? QObject::tr("The process returned") + ":\n\n" % ehushd->errorString() : QString(""));
|
|
}
|
|
}
|
|
|
|
this->showError(explanation);
|
|
}
|
|
} else {
|
|
// HUSH3.conf exists, there's no connection, and the user asked us not to start hushd. Error!
|
|
main->logger->write("Not using embedded and couldn't connect to hushd");
|
|
QString explanation = QString() % QObject::tr("Couldn't connect to hushd configured in HUSH3.conf.\n\n"
|
|
"Not starting embedded hushd because --no-embedded was passed");
|
|
if(isHSC) {
|
|
std::string error = "Not using embedded and couldn't connect to " + HSC_name + "d";
|
|
main->logger->write(error.c_str());
|
|
|
|
std::string conf_error = "Couldn't connect to " + HSC_name + "d configured in " + HSC_ac_name + ".conf.\n\n"
|
|
"Not starting embedded " + HSC_name + "d because --no-embedded was passed";
|
|
QString explanation = QString() % QObject::tr(conf_error.c_str());
|
|
}
|
|
this->showError(explanation);
|
|
} });
|
|
}
|
|
else
|
|
{
|
|
if (Settings::getInstance()->useEmbedded())
|
|
{
|
|
// HUSH3.conf was not found, so create one
|
|
createHushConf();
|
|
}
|
|
else
|
|
{
|
|
// Fall back to manual connect
|
|
doManualConnect();
|
|
}
|
|
}
|
|
}
|
|
|
|
QString randomPassword()
|
|
{
|
|
static const char alphanum[] =
|
|
"0123456789"
|
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
"abcdefghijklmnopqrstuvwxyz";
|
|
|
|
// Assume we have low entropy randomness,
|
|
// so we generate a longer password than we probably need
|
|
const int passwordLength = 32;
|
|
char *s = new char[passwordLength + 1];
|
|
|
|
for (int i = 0; i < passwordLength; ++i)
|
|
{
|
|
#ifdef Q_OS_LINUX
|
|
s[i] = alphanum[randombytes_uniform(sizeof(alphanum))];
|
|
#else
|
|
s[i] = alphanum[rand() % (sizeof(alphanum) - 1)];
|
|
#endif
|
|
}
|
|
|
|
s[passwordLength] = 0;
|
|
return QString::fromStdString(s);
|
|
}
|
|
|
|
/**
|
|
* This will create a new HUSH3.conf and download params if they cannot be found
|
|
*/
|
|
void ConnectionLoader::createHushConf()
|
|
{
|
|
main->logger->write(__func__);
|
|
|
|
auto confLocation = hushConfWritableLocation();
|
|
QFileInfo fi(confLocation);
|
|
|
|
QDialog d(main);
|
|
Ui_createHushConf ui;
|
|
ui.setupUi(&d);
|
|
|
|
QPixmap logo(":/img/hushdlogo.png");
|
|
ui.lblTopIcon->setBasePixmap(logo.scaled(512, 512, Qt::KeepAspectRatio, Qt::SmoothTransformation));
|
|
ui.btnPickDir->setEnabled(false);
|
|
|
|
ui.grpAdvanced->setVisible(false);
|
|
QObject::connect(ui.btnAdvancedConfig, &QPushButton::toggled, [=](bool isVisible)
|
|
{
|
|
ui.grpAdvanced->setVisible(isVisible);
|
|
ui.btnAdvancedConfig->setText(isVisible ? QObject::tr("Hide Advanced Config") : QObject::tr("Show Advanced Config")); });
|
|
|
|
QObject::connect(ui.chkCustomDatadir, &QCheckBox::stateChanged, [=](int chked)
|
|
{
|
|
if (chked == Qt::Checked) {
|
|
ui.btnPickDir->setEnabled(true);
|
|
}
|
|
else {
|
|
ui.btnPickDir->setEnabled(false);
|
|
} });
|
|
|
|
QObject::connect(ui.btnPickDir, &QPushButton::clicked, [=]()
|
|
{
|
|
auto datadir = QFileDialog::getExistingDirectory(main, QObject::tr("Choose data directory"), ui.lblDirName->text(), QFileDialog::ShowDirsOnly);
|
|
if (!datadir.isEmpty()) {
|
|
ui.lblDirName->setText(QDir::toNativeSeparators(datadir));
|
|
} });
|
|
|
|
// Show the dialog
|
|
QString datadir = "";
|
|
bool useTor = false;
|
|
QString torPort = "9050";
|
|
|
|
if (d.exec() == QDialog::Accepted)
|
|
{
|
|
datadir = ui.lblDirName->text();
|
|
useTor = ui.chkUseTor->isChecked();
|
|
torPort = ui.torPort->text();
|
|
}
|
|
|
|
main->logger->write("Creating file " + confLocation);
|
|
QDir().mkpath(fi.dir().absolutePath());
|
|
|
|
QFile file(confLocation);
|
|
if (!file.open(QIODevice::ReadWrite | QIODevice::Truncate))
|
|
{
|
|
std::string error = "Could not create" + HSC_ac_name + ".conf.";
|
|
QString explanation = QString() % (isHSC ? QObject::tr(error.c_str()) : QObject::tr("Could not create HUSH3.conf."));
|
|
main->logger->write(explanation);
|
|
|
|
this->showError(explanation);
|
|
return;
|
|
}
|
|
|
|
QTextStream out(&file);
|
|
|
|
if (isHSC)
|
|
{
|
|
std::string str1 = "# Autogenerated by Hush " + App_title + " ";
|
|
std::string str2 = " " + HSC_website;
|
|
out << str1.c_str() << APP_VERSION << str2.c_str();
|
|
}
|
|
else
|
|
{
|
|
out << "# Autogenerated by Hush SilentDragon " << APP_VERSION << " https://hush.is\n";
|
|
}
|
|
out << "server=1\n";
|
|
out << "rpcpassword=" % randomPassword() << "\n";
|
|
|
|
if (isHSC)
|
|
{
|
|
std::string str1 = "rpcuser=" + HSC_name + "\n";
|
|
std::string str2 = "rpcport=" + HSC_port + "\n";
|
|
out << str1.c_str();
|
|
out << str2.c_str();
|
|
}
|
|
else
|
|
{
|
|
out << "rpcuser=hush\n";
|
|
out << "rpcport=18031\n";
|
|
}
|
|
out << "txindex=1\n";
|
|
out << "addressindex=1\n";
|
|
out << "spentindex=1\n";
|
|
out << "timestampindex=1\n";
|
|
out << "rpcworkqueue=256\n";
|
|
out << "rpcallowip=127.0.0.1\n";
|
|
|
|
// Consolidation is now defaulted to ON for new wallets
|
|
out << "consolidation=1\n";
|
|
|
|
// This is default behavior for hushd 3.6.1 and newer,
|
|
// this helps if older hushd's are being used
|
|
out << "tls=only\n";
|
|
|
|
if (!datadir.isEmpty())
|
|
{
|
|
out << "datadir=" % datadir % "\n";
|
|
}
|
|
if (useTor)
|
|
{
|
|
out << "proxy=127.0.0.1:" << torPort << "\n";
|
|
}
|
|
|
|
file.close();
|
|
|
|
// Now that HUSH3.conf exists, try to autoconnect again
|
|
this->doAutoConnect();
|
|
}
|
|
|
|
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();
|
|
|
|
main->logger->write("All Downloads done");
|
|
this->showInformation(QObject::tr("All Downloads Finished Successfully!"));
|
|
cb();
|
|
return;
|
|
}
|
|
|
|
QUrl url = downloadQueue->dequeue();
|
|
int filesRemaining = downloadQueue->size();
|
|
|
|
QString filename = fnSaveFileName(url);
|
|
QString paramsDir = zkParamsDir();
|
|
|
|
if (QFile(QDir(paramsDir).filePath(filename)).exists())
|
|
{
|
|
main->logger->write(filename + " already exists, skipping");
|
|
doNextDownload(cb);
|
|
|
|
return;
|
|
}
|
|
|
|
// The downloaded file is written to a new name, and then renamed when the operation completes.
|
|
currentOutput = new QFile(QDir(paramsDir).filePath(filename + ".part"));
|
|
|
|
if (!currentOutput->open(QIODevice::WriteOnly))
|
|
{
|
|
main->logger->write("Couldn't open " + currentOutput->fileName() + " for writing");
|
|
this->showError(QObject::tr("Couldn't download params. Please check the help site for more info."));
|
|
}
|
|
main->logger->write("Downloading to " + filename);
|
|
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";
|
|
} else {
|
|
speed /= 1024*1024;
|
|
unit = "MB/s";
|
|
}
|
|
|
|
this->showInformation(
|
|
QObject::tr("Downloading ") % filename % (filesRemaining > 1 ? " ( +" % QString::number(filesRemaining) % QObject::tr(" more remaining )") : QString("")),
|
|
QString::number(done/1024/1024, 'f', 0) % QObject::tr("MB of ") % QString::number(total/1024/1024, 'f', 0) + QObject::tr("MB at ") % QString::number(speed, 'f', 2) % unit); });
|
|
|
|
// Download Finished
|
|
QObject::connect(currentDownload, &QNetworkReply::finished, [=]()
|
|
{
|
|
// Rename file
|
|
main->logger->write("Finished downloading " + filename);
|
|
currentOutput->rename(QDir(paramsDir).filePath(filename));
|
|
|
|
currentOutput->close();
|
|
currentDownload->deleteLater();
|
|
currentOutput->deleteLater();
|
|
|
|
if (currentDownload->error()) {
|
|
main->logger->write("Downloading " + filename + " failed");
|
|
this->showError(QObject::tr("Downloading ") + filename + QObject::tr(" 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()); });
|
|
}
|
|
|
|
bool ConnectionLoader::startEmbeddedHushd()
|
|
{
|
|
if (!Settings::getInstance()->useEmbedded())
|
|
return false;
|
|
|
|
main->logger->write("Trying to start embedded hushd");
|
|
|
|
// Static because it needs to survive even after this method returns.
|
|
static QString processStdErrOutput;
|
|
|
|
if (ehushd != nullptr)
|
|
{
|
|
if (ehushd->state() == QProcess::NotRunning)
|
|
{
|
|
if (!processStdErrOutput.isEmpty())
|
|
{
|
|
QMessageBox::critical(main, QObject::tr("hushd error"), "hushd said: " + processStdErrOutput,
|
|
QMessageBox::Ok);
|
|
}
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
QDir appPath(QCoreApplication::applicationDirPath());
|
|
|
|
#ifdef Q_OS_WIN64
|
|
auto hushdProgram = appPath.absoluteFilePath("hushd.exe");
|
|
// params for DRGX are handled below
|
|
#else
|
|
auto hushdProgram = appPath.absoluteFilePath("hushd");
|
|
if (isHSC)
|
|
{
|
|
std::string daemon = HSC_name + "d";
|
|
hushdProgram = appPath.absoluteFilePath(daemon.c_str());
|
|
}
|
|
#endif
|
|
|
|
// if (!QFile(hushdProgram).exists()) {
|
|
if (!QFile::exists(hushdProgram))
|
|
{
|
|
qDebug() << "Can't find binary at " << hushdProgram;
|
|
main->logger->write("Can't find binary at " + hushdProgram);
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
qDebug() << "Found binary at " << hushdProgram;
|
|
main->logger->write("Found binary at " + hushdProgram);
|
|
}
|
|
|
|
ehushd = std::shared_ptr<QProcess>(new QProcess(main));
|
|
QObject::connect(ehushd.get(), &QProcess::started, [=]()
|
|
{ qDebug() << "Embedded binary started via " << hushdProgram; });
|
|
|
|
QObject::connect(ehushd.get(), QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
|
|
[=](int exitCode, QProcess::ExitStatus exitStatus)
|
|
{
|
|
qDebug() << "hushd finished with code " << exitCode << "," << exitStatus;
|
|
});
|
|
|
|
QObject::connect(ehushd.get(), &QProcess::errorOccurred, [&](QProcess::ProcessError error)
|
|
{
|
|
qDebug() << "Couldn't start hushd!";
|
|
qDebug() << "hushd at " << hushdProgram << ":" << error; });
|
|
|
|
std::weak_ptr<QProcess> weak_obj(ehushd);
|
|
auto ptr_main(main);
|
|
QObject::connect(ehushd.get(), &QProcess::readyReadStandardError, [weak_obj, ptr_main]()
|
|
{
|
|
auto output = weak_obj.lock()->readAllStandardError();
|
|
ptr_main->logger->write("hushd stderr:" + output);
|
|
processStdErrOutput.append(output); });
|
|
|
|
// This string should be the exact arg list seperated by single spaces
|
|
// Could be modified to start different Hush Smart Chains
|
|
QString params = "-tls=only -clientname=GoldenSandtrout"; // "-ac_name=TUSH";
|
|
|
|
/* This is now enabled by default in hushd
|
|
// Binaries come with this file
|
|
if(QFile( QDir(".").filePath("asmap.dat") ).exists()) {
|
|
auto asmap = appPath.absoluteFilePath("asmap.dat");
|
|
params += " -asmap=" + asmap;
|
|
} else {
|
|
qDebug() << "No ASN map file found";
|
|
}
|
|
*/
|
|
if (isHSC)
|
|
{
|
|
// dragonxd bash script cannot be used on windows, so specify exact chain params
|
|
params += QString::fromStdString(HSC_param);
|
|
}
|
|
|
|
QStringList arguments = params.split(" ");
|
|
|
|
// Finally, actually start the full node
|
|
#ifdef Q_OS_LINUX
|
|
qDebug() << "Starting on Linux: " + hushdProgram + " " + params;
|
|
ehushd->start(hushdProgram, arguments);
|
|
#elif defined(Q_OS_DARWIN)
|
|
qDebug() << "Starting on Darwin: " + hushdProgram + " " + params;
|
|
ehushd->start(hushdProgram, arguments);
|
|
#elif defined(Q_OS_WIN64)
|
|
qDebug() << "Starting on Win64: " + hushdProgram + " " + params;
|
|
ehushd->setWorkingDirectory(appPath.absolutePath());
|
|
ehushd->start(hushdProgram, arguments);
|
|
#else
|
|
qDebug() << "Starting on Unknown OS(!): " + hushdProgram + " " + params;
|
|
ehushd->setWorkingDirectory(appPath.absolutePath());
|
|
ehushd->start(hushdProgram, arguments);
|
|
#endif // Q_OS_LINUX
|
|
|
|
main->logger->write("Started via " + hushdProgram + " " + params);
|
|
return true;
|
|
}
|
|
|
|
void ConnectionLoader::doManualConnect()
|
|
{
|
|
auto config = loadFromSettings();
|
|
|
|
if (!config)
|
|
{
|
|
// Nothing configured, show an error
|
|
QString explanation = QString() % QObject::tr("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;
|
|
}
|
|
|
|
auto connection = makeConnection(config);
|
|
refreshHushdState(connection, [=]()
|
|
{
|
|
QString explanation = QString()
|
|
% QObject::tr("Could not connect to hushd configured in settings.\n\n"
|
|
"Please set the host/port and user/password in the Edit->Settings menu.");
|
|
|
|
showError(explanation);
|
|
doRPCSetConnection(nullptr);
|
|
|
|
return; });
|
|
}
|
|
|
|
void ConnectionLoader::doRPCSetConnection(Connection *conn)
|
|
{
|
|
rpc->setEHushd(ehushd);
|
|
rpc->setConnection(conn);
|
|
|
|
d->accept();
|
|
|
|
delete this;
|
|
}
|
|
|
|
Connection *ConnectionLoader::makeConnection(std::shared_ptr<ConnectionConfig> config)
|
|
{
|
|
QNetworkAccessManager *client = new QNetworkAccessManager(main);
|
|
|
|
QUrl myurl;
|
|
myurl.setScheme("http");
|
|
myurl.setHost(config.get()->host);
|
|
myurl.setPort(config.get()->port.toInt());
|
|
|
|
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);
|
|
}
|
|
|
|
void ConnectionLoader::refreshHushdState(Connection *connection, std::function<void(void)> refused)
|
|
{
|
|
qDebug() << __func__ << ": refreshing state";
|
|
|
|
QJsonObject payload = {
|
|
{"jsonrpc", "1.0"},
|
|
{"id", "42"},
|
|
{"method", "getinfo"}};
|
|
connection->doRPC(
|
|
payload,
|
|
[=](auto)
|
|
{
|
|
// Success
|
|
qDebug() << __func__ << ": hushd is online!";
|
|
// Delay 1 second to ensure loading (splash) is seen at least 1 second.
|
|
QTimer::singleShot(1000, [=]()
|
|
{ this->doRPCSetConnection(connection); });
|
|
},
|
|
[=](QNetworkReply *reply, const QJsonValue &res)
|
|
{
|
|
qDebug() << __func__ << ": failed to connect to hushd!";
|
|
// Failed, see what it is.
|
|
auto err = reply->error();
|
|
// qDebug() << err << res;
|
|
|
|
if (err == QNetworkReply::NetworkError::ConnectionRefusedError)
|
|
{
|
|
refused();
|
|
}
|
|
else if (err == QNetworkReply::NetworkError::AuthenticationRequiredError)
|
|
{
|
|
main->logger->write("Authentication failed");
|
|
QString explanation = QString() %
|
|
QObject::tr("Authentication failed. The username / password you specified was "
|
|
"not accepted by hushd. Try changing it in the Edit->Settings menu");
|
|
if (isHSC)
|
|
{
|
|
std::string auth_failed = "Authentication failed. The username / password you specified was not "
|
|
"accepted by " +
|
|
HSC_name + "d. Try changing it in the Edit->Settings menu";
|
|
explanation = QString() %
|
|
QObject::tr(auth_failed.c_str());
|
|
}
|
|
|
|
this->showError(explanation);
|
|
}
|
|
else if (err == QNetworkReply::NetworkError::InternalServerError &&
|
|
!res.isNull())
|
|
{
|
|
// The server is loading, so just poll until it succeeds
|
|
QString status = res["error"].toObject()["message"].toString();
|
|
{
|
|
static int dots = 0;
|
|
status = status.left(status.length() - 3) + QString(".").repeated(dots);
|
|
dots++;
|
|
if (dots > 3)
|
|
dots = 0;
|
|
}
|
|
if (isHSC)
|
|
{
|
|
std::string please_wait = "Your " + HSC_name + "d is starting up. Please wait.";
|
|
this->showInformation(QObject::tr(please_wait.c_str()), status);
|
|
}
|
|
else
|
|
{
|
|
this->showInformation(QObject::tr("Your hushd is starting up. Please wait."), status);
|
|
}
|
|
main->logger->write("Waiting for hushd to come online.");
|
|
// Refresh after one second
|
|
QTimer::singleShot(1000, [=]()
|
|
{ this->refreshHushdState(connection, refused); });
|
|
}
|
|
});
|
|
}
|
|
|
|
// Update the UI with the status
|
|
void ConnectionLoader::showInformation(QString info, QString detail)
|
|
{
|
|
static int rescanCount = 0;
|
|
if (detail.toLower().startsWith("rescan"))
|
|
{
|
|
qDebug() << "showInformation detail = " + detail.toLower();
|
|
rescanCount++;
|
|
}
|
|
|
|
if (rescanCount > 10)
|
|
{
|
|
detail = detail + "\n" + QObject::tr("This may take several hours, grab some popcorn");
|
|
}
|
|
|
|
connD->status->setText(info);
|
|
connD->statusDetail->setText(detail);
|
|
|
|
if (rescanCount < 10)
|
|
main->logger->write(info + ":" + detail);
|
|
}
|
|
|
|
/**
|
|
* Show error will close the loading dialog and show an error.
|
|
*/
|
|
void ConnectionLoader::showError(QString explanation)
|
|
{
|
|
rpc->setEHushd(nullptr);
|
|
rpc->noConnection();
|
|
|
|
QMessageBox::critical(main, QObject::tr("Connection Error"), explanation, QMessageBox::Ok);
|
|
d->close();
|
|
}
|
|
|
|
QString ConnectionLoader::locateHushConfFile()
|
|
{
|
|
|
|
// HSC's have no legacy locations
|
|
if (isHSC)
|
|
{
|
|
auto acname = QString::fromStdString(HSC_ac_name);
|
|
#ifdef Q_OS_LINUX
|
|
auto confLocation = QStandardPaths::locate(QStandardPaths::HomeLocation, QString(".hush/") + acname + "/" + acname + ".conf");
|
|
#elif defined(Q_OS_DARWIN)
|
|
auto confLocation = QStandardPaths::locate(QStandardPaths::HomeLocation, QString("Library/Application Support/Hush/") + acname + "/" + acname + ".conf");
|
|
#else
|
|
auto confLocation = QStandardPaths::locate(QStandardPaths::AppDataLocation, QString("../../Hush/") + acname + "/" + acname + ".conf");
|
|
#endif
|
|
qDebug() << "found conf at " << confLocation;
|
|
return QDir::cleanPath(confLocation);
|
|
}
|
|
|
|
#ifdef Q_OS_LINUX
|
|
auto confLocation = QStandardPaths::locate(QStandardPaths::HomeLocation, ".hush/HUSH3/HUSH3.conf");
|
|
if (!QFile(confLocation).exists())
|
|
{
|
|
// legacy location
|
|
confLocation = QStandardPaths::locate(QStandardPaths::HomeLocation, ".komodo/HUSH3/HUSH3.conf");
|
|
}
|
|
#elif defined(Q_OS_DARWIN)
|
|
auto confLocation = QStandardPaths::locate(QStandardPaths::HomeLocation, "Library/Application Support/Hush/HUSH3/HUSH3.conf");
|
|
if (!QFile(confLocation).exists())
|
|
{
|
|
// legacy location
|
|
confLocation = QStandardPaths::locate(QStandardPaths::HomeLocation, "Library/Application Support/Komodo/HUSH3/HUSH3.conf");
|
|
}
|
|
#else
|
|
auto confLocation = QStandardPaths::locate(QStandardPaths::AppDataLocation, "../../Hush/HUSH3/HUSH3.conf");
|
|
if (!QFile(confLocation).exists())
|
|
{
|
|
// legacy location
|
|
confLocation = QStandardPaths::locate(QStandardPaths::AppDataLocation, "../../Komodo/HUSH3/HUSH3.conf");
|
|
}
|
|
#endif
|
|
|
|
main->logger->write("Found HUSH3.conf at " + QDir::cleanPath(confLocation));
|
|
return QDir::cleanPath(confLocation);
|
|
}
|
|
|
|
// this function is only used for new config files and does not need to know about legacy locations
|
|
QString ConnectionLoader::hushConfWritableLocation()
|
|
{
|
|
#ifdef Q_OS_LINUX
|
|
auto confLocation = QDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)).filePath(".hush/HUSH3/HUSH3.conf");
|
|
if (isHSC)
|
|
{
|
|
std::string conf_location = ".hush/" + HSC_ac_name + "/" + HSC_ac_name + ".conf";
|
|
confLocation = QDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)).filePath(conf_location.c_str());
|
|
}
|
|
#elif defined(Q_OS_DARWIN)
|
|
auto confLocation = QDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)).filePath("Library/Application Support/Hush/HUSH3/HUSH3.conf");
|
|
if (isHSC)
|
|
{
|
|
std::string conf_location = "Library/Application Support/Hush/" + HSC_ac_name + "/" + HSC_ac_name + ".conf";
|
|
confLocation = QDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)).filePath(conf_location.c_str());
|
|
}
|
|
#else
|
|
auto confLocation = QDir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)).filePath("../../Hush/HUSH3/HUSH3.conf");
|
|
if (isHSC)
|
|
{
|
|
std::string conf_location = "../../Hush/" + HSC_ac_name + "/" + HSC_ac_name + ".conf";
|
|
confLocation = QDir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)).filePath(conf_location.c_str());
|
|
}
|
|
#endif
|
|
|
|
main->logger->write("HUSH3.conf writeable location at " + QDir::cleanPath(confLocation));
|
|
return QDir::cleanPath(confLocation);
|
|
}
|
|
|
|
QString ConnectionLoader::zkParamsDir()
|
|
{
|
|
#ifdef Q_OS_LINUX
|
|
// TODO: If /usr/share/hush exists, use that. It should not be assumed writeable
|
|
auto paramsLocation = QDir(QDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)).filePath(".zcash-params"));
|
|
// Debian packages do not install into per-user dirs
|
|
if (!paramsLocation.exists())
|
|
{
|
|
paramsLocation = QDir(QDir("/").filePath("usr/share/hush"));
|
|
}
|
|
#elif defined(Q_OS_DARWIN)
|
|
auto paramsLocation = QDir(QDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)).filePath("Library/Application Support/ZcashParams"));
|
|
#else
|
|
auto paramsLocation = QDir(QDir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)).filePath("../../ZcashParams"));
|
|
#endif
|
|
|
|
if (!paramsLocation.exists())
|
|
{
|
|
main->logger->write("Creating params location at " + paramsLocation.absolutePath());
|
|
QDir().mkpath(paramsLocation.absolutePath());
|
|
}
|
|
|
|
main->logger->write("Found Hush params directory at " + paramsLocation.absolutePath());
|
|
return paramsLocation.absolutePath();
|
|
}
|
|
|
|
bool ConnectionLoader::verifyParams()
|
|
{
|
|
QDir paramsDir(zkParamsDir());
|
|
|
|
// TODO: better error reporting if only 1 file exists or is missing
|
|
qDebug() << "Verifying sapling param files exist";
|
|
|
|
// This list of locations to look must be kept in sync with the list in hushd
|
|
if (QFile(QDir(".").filePath("sapling-output.params")).exists() && QFile(QDir(".").filePath("sapling-spend.params")).exists())
|
|
{
|
|
qDebug() << "Found params in .";
|
|
return true;
|
|
}
|
|
|
|
if (QFile(QDir("..").filePath("sapling-output.params")).exists() && QFile(QDir("..").filePath("sapling-spend.params")).exists())
|
|
{
|
|
qDebug() << "Found params in ..";
|
|
return true;
|
|
}
|
|
|
|
if (QFile(QDir("..").filePath("hush3/sapling-output.params")).exists() && QFile(QDir("..").filePath("hush3/sapling-spend.params")).exists())
|
|
{
|
|
qDebug() << "Found params in ../hush3";
|
|
return true;
|
|
}
|
|
|
|
// this is to support SD on mac in /Applications
|
|
if (QFile(QDir("/Applications").filePath("silentdragon.app/Contents/MacOS/sapling-output.params")).exists() && QFile(QDir("/Applications").filePath("./silentdragon.app/Contents/MacOS/sapling-spend.params")).exists())
|
|
{
|
|
qDebug() << "Found params in /Applications/silentdragon.app/Contents/MacOS";
|
|
return true;
|
|
}
|
|
|
|
// this is to support SD on mac inside a DMG
|
|
if (QFile(QDir("./").filePath("silentdragon.app/Contents/MacOS/sapling-output.params")).exists() && QFile(QDir("./").filePath("./silentdragon.app/Contents/MacOS/sapling-spend.params")).exists())
|
|
{
|
|
qDebug() << "Found params in ./silentdragon.app/Contents/MacOS";
|
|
return true;
|
|
}
|
|
|
|
// this is to support SDX on mac in /Applications
|
|
QString app_name = QString::fromStdString(HSC_app_name);
|
|
if (QFile(QDir("/Applications").filePath("" + app_name + ".app/Contents/MacOS/sapling-output.params")).exists() && QFile(QDir("/Applications").filePath("./" + app_name + ".app/Contents/MacOS/sapling-spend.params")).exists())
|
|
{
|
|
qDebug() << "Found params in /Applications/" + app_name + ".app/Contents/MacOS";
|
|
return true;
|
|
}
|
|
|
|
// this is to support SDX on mac inside a DMG
|
|
if (QFile(QDir("./").filePath("" + app_name + ".app/Contents/MacOS/sapling-output.params")).exists() && QFile(QDir("./").filePath("./" + app_name + ".app/Contents/MacOS/sapling-spend.params")).exists())
|
|
{
|
|
qDebug() << "Found params in ./" + app_name + ".app/Contents/MacOS";
|
|
return true;
|
|
}
|
|
|
|
if (QFile(paramsDir.filePath("sapling-output.params")).exists() && QFile(paramsDir.filePath("sapling-spend.params")).exists())
|
|
{
|
|
qDebug() << "Found params in " << paramsDir;
|
|
return true;
|
|
}
|
|
|
|
qDebug() << "Did not find Sapling params!";
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Try to automatically detect a HUSH3/HUSH3.conf file
|
|
or DRAGONX/DRAGONX.conf in the correct location and load parameters
|
|
*/
|
|
std::shared_ptr<ConnectionConfig> ConnectionLoader::autoDetectHushConf()
|
|
{
|
|
auto confLocation = locateHushConfFile();
|
|
|
|
if (confLocation.isNull())
|
|
{
|
|
// No file, just return with nothing
|
|
return nullptr;
|
|
}
|
|
|
|
QFile file(confLocation);
|
|
if (!file.open(QIODevice::ReadOnly))
|
|
{
|
|
qDebug() << file.errorString();
|
|
return nullptr;
|
|
}
|
|
|
|
QTextStream in(&file);
|
|
|
|
auto hushconf = new ConnectionConfig();
|
|
hushconf->host = "127.0.0.1";
|
|
hushconf->connType = ConnectionType::DetectedConfExternalHushD;
|
|
hushconf->usingHushConf = true;
|
|
hushconf->hushDir = QFileInfo(confLocation).absoluteDir().absolutePath();
|
|
hushconf->hushDaemon = false;
|
|
|
|
Settings::getInstance()->setUsingHushConf(confLocation);
|
|
|
|
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")
|
|
{
|
|
hushconf->rpcuser = value;
|
|
}
|
|
if (name == "rpcpassword")
|
|
{
|
|
hushconf->rpcpassword = value;
|
|
}
|
|
if (name == "rpcport")
|
|
{
|
|
hushconf->port = value;
|
|
}
|
|
if (name == "daemon" && value == "1")
|
|
{
|
|
hushconf->hushDaemon = true;
|
|
}
|
|
if (name == "proxy")
|
|
{
|
|
hushconf->proxy = value;
|
|
}
|
|
if (name == "consolidation")
|
|
{
|
|
hushconf->consolidation = value;
|
|
}
|
|
if (name == "deletetx")
|
|
{
|
|
hushconf->deletetx = value;
|
|
}
|
|
if (name == "zindex")
|
|
{
|
|
hushconf->zindex = value;
|
|
}
|
|
if (name == "testnet" &&
|
|
value == "1" &&
|
|
hushconf->port.isEmpty())
|
|
{
|
|
hushconf->port = "18232";
|
|
}
|
|
}
|
|
|
|
// If rpcport is not in the file, and it was not set by the testnet=1 flag, then go to default
|
|
if (hushconf->port.isEmpty())
|
|
{
|
|
if (isHSC)
|
|
{
|
|
hushconf->port = QString::fromStdString(HSC_port);
|
|
}
|
|
else
|
|
{
|
|
hushconf->port = "18031";
|
|
}
|
|
}
|
|
file.close();
|
|
|
|
// Save to Qsettings
|
|
Settings::getInstance()->saveSettings(
|
|
hushconf->host,
|
|
hushconf->port,
|
|
hushconf->rpcuser,
|
|
hushconf->rpcpassword);
|
|
|
|
// In addition to the HUSH3/HUSH3.conf file, also double check the params.
|
|
|
|
return std::shared_ptr<ConnectionConfig>(hushconf);
|
|
}
|
|
|
|
// Load connection settings from the UI, which indicates an unknown, external hushd
|
|
std::shared_ptr<ConnectionConfig> ConnectionLoader::loadFromSettings()
|
|
{
|
|
qDebug() << __func__ << ": Load data 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())
|
|
{
|
|
qDebug() << __func__ << ": username or password empty, returning null!";
|
|
return nullptr;
|
|
}
|
|
|
|
auto uiConfig = new ConnectionConfig{host, port, username, password, false, false, "", "", "", "", "", ConnectionType::UISettingsZCashD};
|
|
|
|
return std::shared_ptr<ConnectionConfig>(uiConfig);
|
|
}
|
|
|
|
/***********************************************************************************
|
|
* Connection Class
|
|
************************************************************************************/
|
|
Connection::Connection(MainWindow *m, QNetworkAccessManager *c, QNetworkRequest *r,
|
|
std::shared_ptr<ConnectionConfig> conf)
|
|
{
|
|
this->restclient = c;
|
|
this->request = r;
|
|
this->config = conf;
|
|
this->main = m;
|
|
}
|
|
|
|
Connection::~Connection()
|
|
{
|
|
delete restclient;
|
|
delete request;
|
|
}
|
|
|
|
void Connection::doRPC(const QJsonValue &payload, const std::function<void(QJsonValue)> &cb,
|
|
const std::function<void(QNetworkReply *, const QJsonValue &)> &ne)
|
|
{
|
|
if (shutdownInProgress)
|
|
{
|
|
qDebug() << __func__ << ": Ignoring RPC because shutdown in progress";
|
|
return;
|
|
}
|
|
|
|
if (payload.isNull() || payload.isUndefined())
|
|
{
|
|
qDebug() << "no payload! ignoring";
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
qDebug() << __func__ << ": " << payload["method"].toString() << payload;
|
|
}
|
|
|
|
QJsonDocument jd_rpc_call(payload.toObject());
|
|
QByteArray ba_rpc_call = jd_rpc_call.toJson();
|
|
|
|
QNetworkReply *reply = restclient->post(*request, ba_rpc_call);
|
|
|
|
QObject::connect(reply, &QNetworkReply::finished, [=]
|
|
{
|
|
reply->deleteLater();
|
|
if (shutdownInProgress) {
|
|
// Ignoring callback because shutdown in progress
|
|
return;
|
|
}
|
|
|
|
QJsonDocument jd_reply = QJsonDocument::fromJson(reply->readAll());
|
|
QJsonValue parsed;
|
|
|
|
if (jd_reply.isObject())
|
|
parsed = jd_reply.object();
|
|
else
|
|
parsed = jd_reply.array();
|
|
|
|
if (reply->error() != QNetworkReply::NoError) {
|
|
ne(reply, parsed);
|
|
return;
|
|
}
|
|
|
|
if (parsed.isNull()) {
|
|
ne(reply, "Unknown error");
|
|
}
|
|
|
|
cb(parsed["result"]); });
|
|
}
|
|
|
|
void Connection::doRPCWithDefaultErrorHandling(const QJsonValue &payload, const std::function<void(QJsonValue)> &cb)
|
|
{
|
|
doRPC(payload, cb, [=](QNetworkReply *reply, const QJsonValue &parsed)
|
|
{
|
|
if (!parsed.isUndefined() && !parsed["error"].toObject()["message"].isNull()) {
|
|
this->showTxError(parsed["error"].toObject()["message"].toString());
|
|
} else {
|
|
this->showTxError(reply->errorString());
|
|
} });
|
|
}
|
|
|
|
void Connection::doRPCIgnoreError(const QJsonValue &payload, const std::function<void(QJsonValue)> &cb)
|
|
{
|
|
doRPC(payload, cb, [=](auto, auto)
|
|
{
|
|
// Ignored error handling
|
|
});
|
|
}
|
|
|
|
void Connection::showTxError(const QString &error)
|
|
{
|
|
if (error.isNull())
|
|
return;
|
|
|
|
// Prevent multiple dialog boxes from showing, because they're all called async
|
|
static bool shown = false;
|
|
if (shown)
|
|
return;
|
|
|
|
shown = true;
|
|
QMessageBox::critical(main, QObject::tr("Transaction Error"), QObject::tr("There was an error! : ") + "\n\n" + error, QMessageBox::StandardButton::Ok);
|
|
shown = false;
|
|
}
|
|
|
|
/**
|
|
* Prevent all future calls from going through
|
|
*/
|
|
void Connection::shutdown()
|
|
{
|
|
shutdownInProgress = true;
|
|
}
|
|
|