// Copyright 2019-2023 The Hush developers // Released under the GPLv3 #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" #include "sd.h" extern bool isdragonx; 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(isdragonx) { d->setWindowTitle("SilentDragonX"); } 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(isdragonx) { this->showInformation(QObject::tr("Starting embedded dragonxd")); } 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 (isdragonx) { explanation = QString() % QObject::tr("You have dragonxd set to start as a daemon, which can cause problems " "with SilentDragonX\n\n." "Please remove the following line from your DRAGONX.conf and restart SilentDragonX\n" "daemon=1"); } } 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(isdragonx) { explanation = QString() % QObject::tr("Couldn't start the embedded dragonxd.\n\n" "Please try restarting.\n\nIf you previously started hushd with custom arguments, you might need to reset DRAGONX.conf.\n\n" "If all else fails, please run dragonxd manually.") % (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(isdragonx) { main->logger->write("Not using embedded and couldn't connect to dragonxd"); QString explanation = QString() % QObject::tr("Couldn't connect to dragonxd configured in DRAGONX.conf.\n\n" "Not starting embedded dragonxd because --no-embedded was passed"); } 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 torProxy = "127.0.0.1"; QString torPort = "9050"; if (d.exec() == QDialog::Accepted) { datadir = ui.lblDirName->text(); useTor = ui.chkUseTor->isChecked(); torProxy = ui.torProxy->text(); 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)) { QString explanation = QString() % (isdragonx ? QObject::tr("Could not create DRAGONX.conf.") : QObject::tr("Could not create HUSH3.conf.") ); main->logger->write(explanation); this->showError(explanation); return; } QTextStream out(&file); if(isdragonx) { out << "# Autogenerated by Hush SilentDragonX " << APP_VERSION << " https://dragonx.is\n"; } else { out << "# Autogenerated by Hush SilentDragon " << APP_VERSION << " https://hush.is\n"; } out << "server=1\n"; out << "rpcpassword=" % randomPassword() << "\n"; if(isdragonx) { out << "rpcuser=dragonx\n"; out << "rpcport=21769\n"; } 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="<< torProxy << ":" << torPort << "\n"; } file.close(); // Now that HUSH3.conf exists, try to autoconnect again this->doAutoConnect(); } void ConnectionLoader::doNextDownload(std::function 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 (isdragonx) { hushdProgram = appPath.absoluteFilePath("dragonxd"); } #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(new QProcess(main)); QObject::connect(ehushd.get(), &QProcess::started, [=] () { qDebug() << "Embedded binary started via " << hushdProgram; }); QObject::connect(ehushd.get(), QOverload::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 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(isdragonx) { // dragonxd bash script cannot be used on windows, so specify exact chain params params += " -ac_name=DRAGONX -ac_algo=randomx -ac_halving=3500000 -ac_reward=300000000 -ac_blocktime=36 -ac_private=1 -addnode=176.126.87.241"; } 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 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 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(isdragonx) { explanation = QString() % QObject::tr("Authentication failed. The username / password you specified was " "not accepted by dragonxd. Try changing it in the Edit->Settings menu"); } 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(isdragonx) { this->showInformation(QObject::tr("Your dragonxd is starting up. Please wait."), 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 (isdragonx) { auto acname = "DRAGONX"; #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(isdragonx) { confLocation = QDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)).filePath(".hush/DRAGONX/DRAGONX.conf"); } #elif defined(Q_OS_DARWIN) auto confLocation = QDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)).filePath("Library/Application Support/Hush/HUSH3/HUSH3.conf"); if(isdragonx) { confLocation = QDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)).filePath("Library/Application Support/Hush/DRAGONX/DRAGONX.conf"); } #else auto confLocation = QDir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)).filePath("../../Hush/HUSH3/HUSH3.conf"); if(isdragonx) { confLocation = QDir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)).filePath("../../Hush/DRAGONX/DRAGONX.conf"); } #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 if( QFile( QDir("/Applications").filePath("silentdragonx.app/Contents/MacOS/sapling-output.params") ).exists() && QFile( QDir("/Applications").filePath("./silentdragonx.app/Contents/MacOS/sapling-spend.params") ).exists() ) { qDebug() << "Found params in /Applications/silentdragonx.app/Contents/MacOS"; return true; } // this is to support SDX on mac inside a DMG if( QFile( QDir("./").filePath("silentdragonx.app/Contents/MacOS/sapling-output.params") ).exists() && QFile( QDir("./").filePath("./silentdragonx.app/Contents/MacOS/sapling-spend.params") ).exists() ) { qDebug() << "Found params in ./silentdragonx.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 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(isdragonx) { hushconf->port = "21769"; } 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(hushconf); } // Load connection settings from the UI, which indicates an unknown, external hushd std::shared_ptr 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(uiConfig); } /*********************************************************************************** * Connection Class ************************************************************************************/ Connection::Connection(MainWindow* m, QNetworkAccessManager* c, QNetworkRequest* r, std::shared_ptr 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& cb, const std::function& ne) { if (shutdownInProgress) { DEBUG("Ignoring RPC because shutdown in progress"); return; } if(payload.isNull() || payload.isUndefined()) { DEBUG("no payload! ignoring"); return; } else { // this will match importprivkey z_importkey z_importviewingkey importwallet z_importwallet // and some other RPCs that have no GUI // So this code ends up redacting payloads which contain private keys and filenames which contain private keys QRegExp re("import"); //DEBUG("payload.toString==" << payload["method"].toString()); //DEBUG("payload.toString.indexIn==" << re.indexIn(payload["method"].toString()) ); if( re.indexIn(payload["method"].toString()) == -1 ) { DEBUG( payload["method"].toString() << payload ); } else { DEBUG( payload["method"].toString() << " PAYLOAD REDACTED " ); } } 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) { DEBUG("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& cb) { doRPC(payload, cb, [=] (QNetworkReply* reply, const QJsonValue &parsed) { if (!parsed.isUndefined() && !parsed["error"].toObject()["message"].isNull()) { DEBUG("got a parse error"); this->showTxError(parsed["error"].toObject()["message"].toString()); } else { DEBUG("got a reply error"); this->showTxError(reply->errorString()); } }); } void Connection::doRPCIgnoreError(const QJsonValue& payload, const std::function& 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("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; }