2ba0ecf428
Migration from boost to std-library equivalents and removal of unreachable code paths. Touches infrastructure only — no consensus rule or wallet serialization changes. Dead code removed: - IRC bootstrap (irc.cpp/h, 417 lines): orphan from pre-Tor era, no callers. - Alert system (alert.cpp/h + sendalert RPC + Qt UI signal, ~500 lines): retired post-V5 fork; old peers' alert messages now hit the unknown-cmd default branch, logged + ignored. - Legacy P2P handlers in main.cpp: "checkpoint" (already a no-op stub since V5 fork master-key removal), "checkorder"/"reply" (2010-era Receive-by-IP feature), plus their unused supporting structures (CRequestTracker, PushRequest overloads, mapRequests/cs_mapRequests, mapReuseKey). - Unreachable RPCs clearwallettransactions and scanforalltxns (~175 lines): defined in rpcwallet.cpp but never registered in the dispatch table. - Stale -alertnotify CLI help text (option was advertised but never wired). boost::filesystem -> std::filesystem (C++17): - 30 source files, 5 headers. namespace fs = boost::filesystem swapped to namespace fs = std::filesystem; boost::filesystem::ifstream/ofstream replaced with std::ifstream/ofstream (path-aware in C++17); fs::system_complete -> fs::absolute; boost::filesystem::filesystem_error -> std::filesystem::filesystem_error. - Build system: dropped Boost::filesystem from link libs and Boost components; PCH includes updated. - Added explicit <filesystem> includes where types were previously available only transitively (db.h, rpcblockchain.cpp). boost::thread -> std::thread (12 files): - sync.h CCriticalSection/CWaitableCriticalSection now alias std::recursive_mutex/std::mutex. boost::unique_lock and boost::condition_variable / boost::mutex::scoped_lock swapped to std equivalents; sync.cpp boost::thread_specific_ptr -> thread_local std::unique_ptr. - init.cpp boost::thread_group rewritten as std::vector<std::thread> with manual join loop. boost::thread::hardware_concurrency -> std::thread::hardware_concurrency. - main.cpp/wallet.cpp -blocknotify/-walletnotify shell-out threads now use std::thread(...).detach() — fixes a latent bug where modern boost::thread destructor would call std::terminate on the joinable thread. - util.cpp NewThread now catches std::system_error. - No interruption_point/interrupt usage anywhere — pure mechanical swap. boost::chrono / boost::posix_time -> std::chrono (3 of 5 files): - util.h: MilliSleep, GetTimeMillis, GetTimeMicros rewritten on std::chrono (system_clock for epoch math, sleep_for for delays). - snapshotnet.cpp: sleep_for swapped. - DoS_tests.cpp: timing harness uses steady_clock. - Skipped: rpcdump.cpp (boost::posix_time::time_input_facet has no clean std::get_time equivalent) and qt/qtipcserver.cpp (locked to boost::posix_time by boost::interprocess::message_queue::timed_receive). Other housekeeping: - Dropped unnecessary "using namespace boost;" from txdb-leveldb.cpp, txdb-rocksdb.cpp, walletdb.cpp, db.cpp (verified no unqualified boost names in those TUs). - Removed unused extern declaration for clearwallettransactions. Build fixes for non-unity builds on MinGW64/GCC 15: - net.cpp: dropped stale #include "irc.h". - addrman.cpp + main.cpp: explicit <cmath> include for sqrt/pow (was arriving transitively via boost headers). - rpcblockchain.cpp + init.cpp: defensive #undef STRICT/ADVISORY/PERMISSIVE since windows.h macros collide with the Checkpoints:: enum values when std headers reorder include flow. - tor_embed_hooks.cpp: triangles_tor_check_interrupted now polls fShutdown instead of boost::this_thread::interruption_requested (we never used boost interruption — the hook was always effectively a no-op). - snapshotnet.cpp: fs::remove error handle uses std::error_code. - serialize.h: added <ios> for std::ios::badbit/failbit (was relying on transitive include via boost). Note: unity builds currently fail on this branch due to std::byte (C++17) colliding with COM 'byte' typedef from shlobj.h when 'using namespace std;' from earlier files in the unity slice leaks into util.cpp's parse of shlobj.h. Build with -DENABLE_UNITY_BUILD=OFF (the default). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
449 lines
16 KiB
C++
449 lines
16 KiB
C++
#include "introdialog.h"
|
|
#include "util.h"
|
|
#include "bootstrap.h"
|
|
|
|
#include <QSettings>
|
|
#include <QVBoxLayout>
|
|
#include <QHBoxLayout>
|
|
#include <QLabel>
|
|
#include <QFileDialog>
|
|
#include <QDir>
|
|
#include <QMessageBox>
|
|
#include <QDialogButtonBox>
|
|
#include <QProgressDialog>
|
|
#include <QCheckBox>
|
|
#include <QApplication>
|
|
|
|
#include <filesystem>
|
|
|
|
#include <set>
|
|
|
|
IntroDialog::IntroDialog(QWidget *parent) :
|
|
QDialog(parent)
|
|
{
|
|
setWindowTitle("Triangles");
|
|
setMinimumWidth(520);
|
|
|
|
// Match existing Triangles dark theme
|
|
setStyleSheet(
|
|
"QDialog { background-color: #000; color: #f26522; }"
|
|
"QLabel { color: #f26522; }"
|
|
"QRadioButton { color: #f26522; }"
|
|
"QRadioButton::indicator { border: 1px solid #f26522; background-color: #000; width: 12px; height: 12px; border-radius: 7px; }"
|
|
"QRadioButton::indicator:checked { background-color: #f26522; }"
|
|
"QLineEdit { background-color: #1c1c1c; border: 1px solid #f26522; color: #f26522; padding: 4px; }"
|
|
"QPushButton { background-color: #000; color: #f26522; border: 1px solid #f26522; padding: 4px 16px; min-height: 20px; }"
|
|
"QPushButton:hover { background-color: #61280E; }"
|
|
);
|
|
|
|
defaultDataDir = QString::fromStdString(GetDefaultDataDir().string());
|
|
|
|
QVBoxLayout *mainLayout = new QVBoxLayout(this);
|
|
mainLayout->setContentsMargins(20, 20, 20, 20);
|
|
mainLayout->setSpacing(12);
|
|
|
|
// Welcome header
|
|
QLabel *welcomeLabel = new QLabel(tr("Welcome to Triangles!"));
|
|
welcomeLabel->setStyleSheet("font-size: 16px; font-weight: bold; color: #f26522;");
|
|
mainLayout->addWidget(welcomeLabel);
|
|
|
|
// Description
|
|
QLabel *descLabel = new QLabel(tr(
|
|
"Triangles will store its blockchain data, wallet, and configuration in a data directory. "
|
|
"You can use the default directory or choose a custom location. "
|
|
"The data directory requires several hundred MB of free space."
|
|
));
|
|
descLabel->setWordWrap(true);
|
|
mainLayout->addWidget(descLabel);
|
|
|
|
mainLayout->addSpacing(8);
|
|
|
|
// Default directory radio
|
|
defaultRadio = new QRadioButton(tr("Use the default data directory"));
|
|
defaultRadio->setChecked(true);
|
|
mainLayout->addWidget(defaultRadio);
|
|
|
|
// Show default path
|
|
QLabel *defaultPathLabel = new QLabel(defaultDataDir);
|
|
defaultPathLabel->setStyleSheet("color: #999; margin-left: 24px; font-size: 11px;");
|
|
mainLayout->addWidget(defaultPathLabel);
|
|
|
|
mainLayout->addSpacing(4);
|
|
|
|
// Custom directory radio
|
|
customRadio = new QRadioButton(tr("Use a custom data directory:"));
|
|
mainLayout->addWidget(customRadio);
|
|
|
|
// Path input + browse button
|
|
QHBoxLayout *pathLayout = new QHBoxLayout();
|
|
pathLayout->setContentsMargins(24, 0, 0, 0);
|
|
|
|
pathEdit = new QLineEdit(defaultDataDir);
|
|
pathEdit->setEnabled(false);
|
|
pathLayout->addWidget(pathEdit);
|
|
|
|
browseButton = new QPushButton(tr("Browse..."));
|
|
browseButton->setEnabled(false);
|
|
pathLayout->addWidget(browseButton);
|
|
|
|
mainLayout->addLayout(pathLayout);
|
|
|
|
// Free space label
|
|
freeSpaceLabel = new QLabel();
|
|
freeSpaceLabel->setStyleSheet("color: #999; margin-left: 24px; font-size: 11px;");
|
|
mainLayout->addWidget(freeSpaceLabel);
|
|
|
|
mainLayout->addStretch(1);
|
|
|
|
// OK / Cancel buttons
|
|
QHBoxLayout *buttonLayout = new QHBoxLayout();
|
|
buttonLayout->addStretch(1);
|
|
|
|
QPushButton *okButton = new QPushButton(tr("OK"));
|
|
QPushButton *cancelButton = new QPushButton(tr("Cancel"));
|
|
|
|
buttonLayout->addWidget(okButton);
|
|
buttonLayout->addWidget(cancelButton);
|
|
mainLayout->addLayout(buttonLayout);
|
|
|
|
// Connections
|
|
connect(defaultRadio, SIGNAL(toggled(bool)), this, SLOT(on_defaultRadio_toggled(bool)));
|
|
connect(browseButton, SIGNAL(clicked()), this, SLOT(on_browseButton_clicked()));
|
|
connect(pathEdit, SIGNAL(textChanged(QString)), this, SLOT(updateFreeSpace()));
|
|
connect(okButton, SIGNAL(clicked()), this, SLOT(accept()));
|
|
connect(cancelButton, SIGNAL(clicked()), this, SLOT(reject()));
|
|
|
|
updateFreeSpace();
|
|
}
|
|
|
|
QString IntroDialog::getDataDirectory() const
|
|
{
|
|
if (defaultRadio->isChecked())
|
|
return defaultDataDir;
|
|
return pathEdit->text();
|
|
}
|
|
|
|
void IntroDialog::setDataDirectory(const QString &dir)
|
|
{
|
|
pathEdit->setText(dir);
|
|
if (dir == defaultDataDir) {
|
|
defaultRadio->setChecked(true);
|
|
} else {
|
|
customRadio->setChecked(true);
|
|
}
|
|
}
|
|
|
|
void IntroDialog::on_browseButton_clicked()
|
|
{
|
|
QString dir = QFileDialog::getExistingDirectory(this, tr("Choose data directory"), pathEdit->text());
|
|
if (!dir.isEmpty())
|
|
pathEdit->setText(dir);
|
|
}
|
|
|
|
void IntroDialog::on_defaultRadio_toggled(bool checked)
|
|
{
|
|
pathEdit->setEnabled(!checked);
|
|
browseButton->setEnabled(!checked);
|
|
if (checked)
|
|
pathEdit->setText(defaultDataDir);
|
|
updateFreeSpace();
|
|
}
|
|
|
|
void IntroDialog::updateFreeSpace()
|
|
{
|
|
QString path = getDataDirectory();
|
|
std::filesystem::path fsPath(path.toStdString());
|
|
|
|
// Walk up to find an existing parent
|
|
try {
|
|
while (!fsPath.empty() && !std::filesystem::exists(fsPath))
|
|
fsPath = fsPath.parent_path();
|
|
|
|
if (!fsPath.empty()) {
|
|
std::filesystem::space_info si = std::filesystem::space(fsPath);
|
|
double freeGB = (double)si.available / (1024.0 * 1024.0 * 1024.0);
|
|
freeSpaceLabel->setText(tr("Free space: %1 GB").arg(QString::number(freeGB, 'f', 2)));
|
|
} else {
|
|
freeSpaceLabel->setText(tr("Cannot determine free space"));
|
|
}
|
|
} catch (const std::filesystem::filesystem_error &) {
|
|
freeSpaceLabel->setText(tr("Cannot determine free space"));
|
|
}
|
|
}
|
|
|
|
bool IntroDialog::pickDataDirectory()
|
|
{
|
|
namespace fs = std::filesystem;
|
|
|
|
QSettings settings;
|
|
// If -datadir was passed on the command line, skip the dialog entirely
|
|
if (mapArgs.count("-datadir"))
|
|
return true;
|
|
|
|
QString dataDir = settings.value("strDataDir", "").toString();
|
|
|
|
if (dataDir.isEmpty()) {
|
|
// First run - show the dialog
|
|
IntroDialog dlg;
|
|
if (dlg.exec() != QDialog::Accepted)
|
|
return false;
|
|
|
|
dataDir = dlg.getDataDirectory();
|
|
settings.setValue("strDataDir", dataDir);
|
|
}
|
|
|
|
// Check for pending data directory migration
|
|
if (settings.value("fPendingDataDirMigration", false).toBool()) {
|
|
QString oldDir = settings.value("strDataDirPrevious", "").toString();
|
|
if (!oldDir.isEmpty() && oldDir != dataDir) {
|
|
if (!migrateDataDirectory(oldDir, dataDir)) {
|
|
// Migration failed - revert to old directory
|
|
QMessageBox::warning(0, "Triangles",
|
|
QString("Data directory migration failed.\nContinuing with the previous directory:\n%1")
|
|
.arg(oldDir));
|
|
dataDir = oldDir;
|
|
settings.setValue("strDataDir", oldDir);
|
|
}
|
|
}
|
|
// Clear migration state regardless
|
|
settings.remove("strDataDirPrevious");
|
|
settings.setValue("fPendingDataDirMigration", false);
|
|
}
|
|
|
|
// If the saved path is the default, don't set -datadir (let normal defaults work)
|
|
QString defaultDir = QString::fromStdString(GetDefaultDataDir().string());
|
|
if (dataDir != defaultDir) {
|
|
mapArgs["-datadir"] = dataDir.toStdString();
|
|
}
|
|
|
|
// Ensure the directory exists
|
|
try {
|
|
fs::create_directories(fs::path(dataDir.toStdString()));
|
|
} catch (const fs::filesystem_error &) {
|
|
QMessageBox::critical(0, "Triangles",
|
|
QString("Error: Could not create data directory \"%1\".").arg(dataDir));
|
|
return false;
|
|
}
|
|
|
|
// Auto-bootstrap: if no blockchain data exists, download automatically.
|
|
// If data exists, offer optional re-download (unless user checked "don't ask again").
|
|
fs::path dataDirPath(dataDir.toStdString());
|
|
bool needsBootstrap = Bootstrap::NeedsBootstrap(dataDirPath);
|
|
bool userWantsBootstrap = false;
|
|
|
|
if (needsBootstrap)
|
|
{
|
|
// No blockchain data — bootstrap automatically, just inform the user
|
|
QMessageBox::information(0, "Triangles",
|
|
"No blockchain data found.\n\n"
|
|
"Downloading the latest blockchain snapshot automatically.\n"
|
|
"This will only take a few minutes.");
|
|
userWantsBootstrap = true;
|
|
}
|
|
else if (!settings.value("bootstrapDontAsk", false).toBool())
|
|
{
|
|
QMessageBox msgBox;
|
|
msgBox.setWindowTitle("Triangles");
|
|
msgBox.setText(
|
|
"Would you like to download the latest blockchain snapshot?\n\n"
|
|
"This will download the blockchain data from the Triangles network "
|
|
"and replace any existing chain data in your data directory.\n\n"
|
|
"Click Yes to download, or No to sync from the network.");
|
|
msgBox.setIcon(QMessageBox::Question);
|
|
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
|
|
msgBox.setDefaultButton(QMessageBox::Yes);
|
|
QCheckBox *dontAskBox = new QCheckBox("Don't show this again");
|
|
msgBox.setCheckBox(dontAskBox);
|
|
|
|
int ret = msgBox.exec();
|
|
|
|
if (dontAskBox->isChecked())
|
|
settings.setValue("bootstrapDontAsk", true);
|
|
|
|
userWantsBootstrap = (ret == QMessageBox::Yes);
|
|
}
|
|
|
|
if (userWantsBootstrap)
|
|
{
|
|
std::string host = Bootstrap::DEFAULT_HOST;
|
|
std::string strError;
|
|
|
|
QProgressDialog progress("Downloading blockchain snapshot...", "Cancel",
|
|
0, 100, 0);
|
|
progress.setWindowTitle("Triangles - Bootstrap");
|
|
progress.setWindowModality(Qt::ApplicationModal);
|
|
progress.setMinimumDuration(0);
|
|
progress.setValue(0);
|
|
|
|
auto progressFn = [&progress](int64_t bytesDownloaded, int64_t totalBytes) {
|
|
if (totalBytes > 0) {
|
|
int pct = (int)((bytesDownloaded * 100) / totalBytes);
|
|
progress.setValue(pct);
|
|
progress.setLabelText(
|
|
QString("Downloading blockchain snapshot... %1 MB / %2 MB")
|
|
.arg(bytesDownloaded / (1024*1024))
|
|
.arg(totalBytes / (1024*1024)));
|
|
} else {
|
|
progress.setLabelText(
|
|
QString("Downloading blockchain snapshot... %1 MB")
|
|
.arg(bytesDownloaded / (1024*1024)));
|
|
}
|
|
QApplication::processEvents();
|
|
};
|
|
|
|
bool success = Bootstrap::DownloadBootstrap(host, dataDirPath, progressFn, strError);
|
|
if (!success) {
|
|
QMessageBox::warning(0, "Triangles",
|
|
QString("Could not download blockchain snapshot:\n%1\n\n"
|
|
"The wallet will sync from the network instead.")
|
|
.arg(QString::fromStdString(strError)));
|
|
} else {
|
|
progress.setValue(100);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void copyDirectoryRecursive(const std::filesystem::path& src,
|
|
const std::filesystem::path& dst)
|
|
{
|
|
namespace fs = std::filesystem;
|
|
fs::create_directories(dst);
|
|
for (fs::directory_iterator it(src), end; it != end; ++it) {
|
|
fs::path dstChild = dst / it->path().filename();
|
|
if (fs::is_directory(it->path())) {
|
|
copyDirectoryRecursive(it->path(), dstChild);
|
|
} else {
|
|
fs::copy_file(it->path(), dstChild, fs::copy_options::overwrite_existing);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool IntroDialog::migrateDataDirectory(const QString& oldPath, const QString& newPath)
|
|
{
|
|
namespace fs = std::filesystem;
|
|
|
|
fs::path srcDir(oldPath.toStdString());
|
|
fs::path dstDir(newPath.toStdString());
|
|
|
|
if (!fs::exists(srcDir) || !fs::is_directory(srcDir))
|
|
return false;
|
|
|
|
// Create destination directory
|
|
try {
|
|
fs::create_directories(dstDir);
|
|
} catch (const fs::filesystem_error& e) {
|
|
printf("Migration: Cannot create destination directory: %s\n", e.what());
|
|
return false;
|
|
}
|
|
|
|
// Check free space
|
|
try {
|
|
quint64 srcSize = 0;
|
|
for (fs::recursive_directory_iterator it(srcDir), end; it != end; ++it) {
|
|
if (fs::is_regular_file(*it))
|
|
srcSize += fs::file_size(*it);
|
|
}
|
|
fs::space_info si = fs::space(dstDir);
|
|
if (si.available < srcSize + (50 * 1024 * 1024)) { // 50MB headroom
|
|
printf("Migration: Insufficient disk space. Need %llu, have %llu\n",
|
|
(unsigned long long)srcSize, (unsigned long long)si.available);
|
|
return false;
|
|
}
|
|
} catch (const fs::filesystem_error& e) {
|
|
printf("Migration: Cannot check disk space: %s\n", e.what());
|
|
return false;
|
|
}
|
|
|
|
// Files/directories to skip during copy
|
|
static const std::set<std::string> skipFiles = {
|
|
".lock",
|
|
"debug.log",
|
|
"db.log",
|
|
};
|
|
|
|
// Show progress dialog
|
|
QProgressDialog progress("Moving data directory...", QString(), 0, 0, 0);
|
|
progress.setWindowTitle("Triangles - Data Migration");
|
|
progress.setWindowModality(Qt::ApplicationModal);
|
|
progress.setMinimumDuration(0);
|
|
progress.setCancelButton(0);
|
|
progress.show();
|
|
QApplication::processEvents();
|
|
|
|
// Phase 1: Copy wallet.dat FIRST (most critical file)
|
|
fs::path walletSrc = srcDir / "wallet.dat";
|
|
fs::path walletDst = dstDir / "wallet.dat";
|
|
if (fs::exists(walletSrc)) {
|
|
progress.setLabelText("Copying wallet.dat...");
|
|
QApplication::processEvents();
|
|
try {
|
|
// Copy to temp name first, then rename for atomicity
|
|
fs::path walletTmp = dstDir / "wallet.dat.migrating";
|
|
fs::copy_file(walletSrc, walletTmp, fs::copy_options::overwrite_existing);
|
|
|
|
// Verify copy by checking file size
|
|
if (fs::file_size(walletTmp) != fs::file_size(walletSrc)) {
|
|
fs::remove(walletTmp);
|
|
printf("Migration: wallet.dat copy size mismatch!\n");
|
|
return false;
|
|
}
|
|
|
|
// Rename into place
|
|
if (fs::exists(walletDst))
|
|
fs::remove(walletDst);
|
|
fs::rename(walletTmp, walletDst);
|
|
} catch (const fs::filesystem_error& e) {
|
|
printf("Migration: Failed to copy wallet.dat: %s\n", e.what());
|
|
return false; // Abort - wallet is critical
|
|
}
|
|
}
|
|
|
|
// Phase 2: Copy everything else
|
|
int filesCopied = 0;
|
|
try {
|
|
for (fs::directory_iterator it(srcDir), end; it != end; ++it) {
|
|
std::string filename = it->path().filename().string();
|
|
|
|
// Skip special files
|
|
if (skipFiles.count(filename))
|
|
continue;
|
|
|
|
// Skip wallet.dat (already copied)
|
|
if (filename == "wallet.dat")
|
|
continue;
|
|
|
|
fs::path dst = dstDir / filename;
|
|
|
|
progress.setLabelText(QString("Copying %1...").arg(QString::fromStdString(filename)));
|
|
QApplication::processEvents();
|
|
|
|
if (fs::is_directory(it->path())) {
|
|
copyDirectoryRecursive(it->path(), dst);
|
|
} else {
|
|
fs::copy_file(it->path(), dst, fs::copy_options::overwrite_existing);
|
|
}
|
|
filesCopied++;
|
|
}
|
|
} catch (const fs::filesystem_error& e) {
|
|
// Non-wallet copy failure: log but don't abort
|
|
// Chain data can be re-synced; wallet was already safely copied
|
|
printf("Migration: Warning: failed to copy some files: %s\n", e.what());
|
|
}
|
|
|
|
// Phase 3: Rename old wallet.dat as safety backup (don't delete old dir)
|
|
try {
|
|
if (fs::exists(walletSrc)) {
|
|
fs::rename(walletSrc, srcDir / "wallet.dat.bak-migrated");
|
|
}
|
|
} catch (...) {
|
|
// Not critical
|
|
}
|
|
|
|
progress.close();
|
|
printf("Migration: Successfully copied %d items from %s to %s\n",
|
|
filesCopied, srcDir.string().c_str(), dstDir.string().c_str());
|
|
return true;
|
|
}
|