diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml
index 20951c8..ae24811 100644
--- a/.github/workflows/build-all.yml
+++ b/.github/workflows/build-all.yml
@@ -153,14 +153,15 @@ jobs:
New-Item -ItemType Directory -Path tor-extract -Force
tar -xzf tor-bundle.tar.gz -C tor-extract
New-Item -ItemType Directory -Path tor-files -Force
- Copy-Item tor-extract/tor/tor.exe tor-files/
- Copy-Item tor-extract/tor/tor-gencert.exe tor-files/ -ErrorAction SilentlyContinue
+ Copy-Item -Recurse tor-extract/tor/* tor-files/
if (Test-Path tor-extract/tor/pluggable_transports) {
- Copy-Item -Recurse tor-extract/tor/pluggable_transports tor-files/
+ Copy-Item -Recurse tor-extract/tor/pluggable_transports tor-files/pluggable_transports -Force
}
if (Test-Path tor-extract/data) {
Copy-Item -Recurse tor-extract/data tor-files/data
}
+ Write-Host "Bundled Tor runtime files:"
+ Get-ChildItem -Recurse tor-files | Select-Object FullName
- name: Install NSIS via MSYS2
run: pacman -S --noconfirm mingw-w64-x86_64-nsis
@@ -240,7 +241,7 @@ jobs:
Invoke-WebRequest -Uri "https://archive.torproject.org/tor-package-archive/torbrowser/${TOR_VERSION}/tor-expert-bundle-windows-x86_64-${TOR_VERSION}.tar.gz" -OutFile tor-bundle.tar.gz
New-Item -ItemType Directory -Path tor-extract -Force
tar -xzf tor-bundle.tar.gz -C tor-extract
- Copy-Item tor-extract/tor/tor.exe daemon-dist/tor/
+ Copy-Item -Recurse tor-extract/tor/* daemon-dist/tor/
if (Test-Path tor-extract/data) {
Copy-Item -Recurse tor-extract/data daemon-dist/tor/data
}
diff --git a/src/clientversion.h b/src/clientversion.h
index fac44fd..25698c8 100644
--- a/src/clientversion.h
+++ b/src/clientversion.h
@@ -8,7 +8,7 @@
// These need to be macros, as version.cpp's and triangles-qt.rc's voodoo requires it
#define CLIENT_VERSION_MAJOR 5
#define CLIENT_VERSION_MINOR 7
-#define CLIENT_VERSION_REVISION 6
+#define CLIENT_VERSION_REVISION 7
#define CLIENT_VERSION_BUILD 0
// Converts the parameter X to a string after macro replacement on X has been performed.
diff --git a/src/init.cpp b/src/init.cpp
index ee42453..4ea73df 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -1223,7 +1223,10 @@ bool AppInit2()
fUseUPnP = false;
#endif
} else {
- return InitError(_("Tor failed to start. Triangles requires Tor to operate."));
+ std::string torError = CTorEmbedded::GetInstance()->GetLastError();
+ if (torError.empty())
+ torError = "No detailed Tor startup error was recorded.";
+ return InitError(strprintf(_("Tor failed to start. Triangles requires Tor to operate.\n\nDetails: %s"), torError));
}
// Initialize Tor V3 identity (Ed25519 keys, onion address)
diff --git a/src/qt/forms/overviewpage.ui b/src/qt/forms/overviewpage.ui
index c200368..0871af7 100644
--- a/src/qt/forms/overviewpage.ui
+++ b/src/qt/forms/overviewpage.ui
@@ -678,6 +678,43 @@ QWidget#line {
+ -
+
+
+
+ 10
+
+
+
+ Wallet Onion:
+
+
+
+ -
+
+
+
+ 75
+ true
+
+
+
+ IBeamCursor
+
+
+ This wallet's Tor .onion address.
+
+
+ Initializing Tor identity...
+
+
+ true
+
+
+ Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse
+
+
+
diff --git a/src/qt/overviewpage.cpp b/src/qt/overviewpage.cpp
index 62e92fb..9431b38 100644
--- a/src/qt/overviewpage.cpp
+++ b/src/qt/overviewpage.cpp
@@ -8,9 +8,12 @@
#include "transactionfilterproxy.h"
#include "guiutil.h"
#include "guiconstants.h"
+#include "tor/tor_embedded.h"
+#include "tor/onion_v3.h"
#include
#include
+#include
#define DECORATION_SIZE 64
#define NUM_ITEMS 5
@@ -88,6 +91,7 @@ public:
OverviewPage::OverviewPage(QWidget *parent) :
QWidget(parent),
ui(new Ui::OverviewPage),
+ clientModel(0),
currentBalance(-1),
currentStake(0),
currentUnconfirmedBalance(-1),
@@ -112,6 +116,11 @@ OverviewPage::OverviewPage(QWidget *parent) :
// start with displaying the "out of sync" warnings
showOutOfSyncWarning(true);
+
+ QTimer *onionRefreshTimer = new QTimer(this);
+ connect(onionRefreshTimer, SIGNAL(timeout()), this, SLOT(updateOnionAddress()));
+ onionRefreshTimer->start(5000);
+ updateOnionAddress();
}
void OverviewPage::handleTransactionClicked(const QModelIndex &index)
@@ -125,6 +134,12 @@ OverviewPage::~OverviewPage()
delete ui;
}
+void OverviewPage::setClientModel(ClientModel *clientModel)
+{
+ this->clientModel = clientModel;
+ updateOnionAddress();
+}
+
void OverviewPage::setBalance(qint64 balance, qint64 stake, qint64 unconfirmedBalance, qint64 immatureBalance)
{
if (!model || !model->getOptionsModel())
@@ -213,3 +228,20 @@ void OverviewPage::showOutOfSyncWarning(bool fShow)
ui->labelWalletStatus->setVisible(fShow);
ui->labelTransactionsStatus->setVisible(fShow);
}
+
+void OverviewPage::updateOnionAddress()
+{
+ std::string onionAddress = CTorV3Manager::GetInstance()->GetWalletOnionAddress();
+ if (onionAddress.empty()) {
+ onionAddress = CTorEmbedded::GetInstance()->GetOnionAddress();
+ }
+
+ if (onionAddress.empty()) {
+ ui->labelOnionAddress->setText(tr("Initializing Tor identity..."));
+ ui->labelOnionAddress->setToolTip(tr("The wallet's .onion address will appear here once Tor and the wallet identity are ready."));
+ return;
+ }
+
+ ui->labelOnionAddress->setText(QString::fromStdString(onionAddress));
+ ui->labelOnionAddress->setToolTip(tr("This wallet's Tor .onion address."));
+}
diff --git a/src/qt/overviewpage.h b/src/qt/overviewpage.h
index 0a6f038..a6beade 100644
--- a/src/qt/overviewpage.h
+++ b/src/qt/overviewpage.h
@@ -31,6 +31,7 @@ public:
public slots:
void setBalance(qint64 balance, qint64 stake, qint64 unconfirmedBalance, qint64 immatureBalance);
void setTransactionSyncState(bool syncing);
+ void updateOnionAddress();
signals:
void transactionClicked(const QModelIndex &index);
diff --git a/src/qt/trianglesgui.cpp b/src/qt/trianglesgui.cpp
index bd4cb2c..f9405eb 100644
--- a/src/qt/trianglesgui.cpp
+++ b/src/qt/trianglesgui.cpp
@@ -567,6 +567,7 @@ void TrianglesGUI::setClientModel(ClientModel *clientModel)
if (rpcConsole)
rpcConsole->setClientModel(clientModel);
+ overviewPage->setClientModel(clientModel);
addressBookPage->setOptionsModel(clientModel->getOptionsModel());
receiveCoinsPage->setOptionsModel(clientModel->getOptionsModel());
}
diff --git a/src/tor/tor_embedded.cpp b/src/tor/tor_embedded.cpp
index f8d4717..fe7b18e 100644
--- a/src/tor/tor_embedded.cpp
+++ b/src/tor/tor_embedded.cpp
@@ -114,6 +114,7 @@ bool CTorEmbedded::Start(int socks, int hsPort, bool enableHiddenService)
{
if (running.load()) return true;
+ lastError.clear();
socksPort = socks;
hiddenServiceEnabled = enableHiddenService;
hiddenServicePort = hiddenServiceEnabled ? hsPort : 0;
@@ -202,11 +203,13 @@ bool CTorEmbedded::Start(int socks, int hsPort, bool enableHiddenService)
}
if (!running.load()) {
+ lastError = "Embedded Tor thread exited during bootstrap before the SOCKS proxy became available.";
printf("ERROR: Embedded Tor thread exited during bootstrap\n");
return false;
}
}
+ lastError = strprintf("Embedded Tor did not expose SOCKS port %d within 60 seconds.", socksPort);
printf("WARNING: Embedded Tor started but SOCKS not ready after 60s (still bootstrapping)\n");
return true;
}
@@ -241,8 +244,12 @@ bool CTorEmbedded::Start(int socks, int hsPort, bool enableHiddenService)
hiddenServiceEnabled = enableHiddenService;
hiddenServicePort = hiddenServiceEnabled ? hsPort : 0;
onionHostname.clear();
+ lastError.clear();
torDataDir = (::GetDataDir() / "tor_data").string();
running.store(StartTorProcess(torDataDir, socksPort, hiddenServicePort, hiddenServiceEnabled));
+ if (!running.load()) {
+ lastError = CTorProcess::GetInstance()->GetLastError();
+ }
return running.load();
}
diff --git a/src/tor/tor_embedded.h b/src/tor/tor_embedded.h
index 18bd739..abe26a5 100644
--- a/src/tor/tor_embedded.h
+++ b/src/tor/tor_embedded.h
@@ -19,6 +19,7 @@ private:
bool hiddenServiceEnabled;
std::string torDataDir;
std::string onionHostname;
+ std::string lastError;
public:
static CTorEmbedded* GetInstance();
@@ -43,6 +44,8 @@ public:
// Get our .onion address (available after bootstrap)
std::string GetOnionAddress() const { return onionHostname; }
+ std::string GetLastError() const { return lastError; }
+ void SetLastError(const std::string& value) { lastError = value; }
// Get the hidden service port
int GetHiddenServicePort() const { return hiddenServicePort; }
diff --git a/src/tor/tor_process.cpp b/src/tor/tor_process.cpp
index 7f32027..2b97e07 100644
--- a/src/tor/tor_process.cpp
+++ b/src/tor/tor_process.cpp
@@ -19,11 +19,13 @@
#include
#include
#include
+#include
#ifdef WIN32
#include
#include
#include
+#include
#else
#include
#include
@@ -35,6 +37,28 @@
namespace fs = boost::filesystem;
+static std::string ReadTailLines(const fs::path& filePath, size_t maxLines)
+{
+ std::ifstream in(filePath.string().c_str());
+ if (!in.is_open()) return "";
+
+ std::vector lines;
+ std::string line;
+ while (std::getline(in, line)) {
+ lines.push_back(line);
+ if (lines.size() > maxLines) {
+ lines.erase(lines.begin());
+ }
+ }
+
+ std::ostringstream out;
+ for (size_t i = 0; i < lines.size(); ++i) {
+ if (i) out << " | ";
+ out << lines[i];
+ }
+ return out.str();
+}
+
static CTorProcess* torProcessInstance = nullptr;
CTorProcess* CTorProcess::GetInstance()
@@ -199,6 +223,10 @@ bool CTorProcess::WriteTorrc()
fs::create_directories(torStateDir);
torrc << "DataDirectory " << torStateDir.string() << "\n";
+ // Persistent Tor log for post-mortem debugging on user machines.
+ fs::path torLogPath = dataPath / "tor.log";
+ torrc << "Log notice file " << torLogPath.string() << "\n";
+
if (hiddenServiceEnabled) {
// V3 hidden service so this node is reachable via .onion
torrc << "HiddenServiceDir " << hsDir.string() << "\n";
@@ -232,6 +260,7 @@ bool CTorProcess::WriteTorrc()
bool CTorProcess::Start(const std::string& dataDir, int socks, int hsPort, bool enableHiddenService)
{
+ lastError.clear();
socksPort = socks;
hiddenServiceEnabled = enableHiddenService;
hiddenServicePort = hiddenServiceEnabled ? hsPort : 0;
@@ -240,6 +269,7 @@ bool CTorProcess::Start(const std::string& dataDir, int socks, int hsPort, bool
// Check if something is already listening on our SOCKS port
if (IsPortInUse(socksPort)) {
printf("Tor SOCKS port %d already in use - assuming Tor is running\n", socksPort);
+ lastError = strprintf("SOCKS port %d is already in use; assuming an existing Tor instance is serving it.", socksPort);
running = true;
return true;
}
@@ -247,6 +277,7 @@ bool CTorProcess::Start(const std::string& dataDir, int socks, int hsPort, bool
// Find Tor binary
torBinaryPath = FindTorBinary();
if (torBinaryPath.empty()) {
+ lastError = "Tor binary was not found in the bundled install or standard search paths.";
printf("WARNING: Tor binary not found. Install Tor for .onion connectivity.\n");
printf(" Windows: Download from https://www.torproject.org/download/tor/\n");
printf(" Linux: apt install tor or yum install tor\n");
@@ -256,6 +287,7 @@ bool CTorProcess::Start(const std::string& dataDir, int socks, int hsPort, bool
// Write configuration
if (!WriteTorrc()) {
+ lastError = strprintf("Failed to write Tor configuration to %s", torrcPath.c_str());
printf("ERROR: Failed to write Tor configuration\n");
return false;
}
@@ -282,7 +314,9 @@ bool CTorProcess::Start(const std::string& dataDir, int socks, int hsPort, bool
NULL, NULL,
&si, &pi))
{
- printf("ERROR: Failed to start Tor process (error %lu)\n", GetLastError());
+ DWORD err = GetLastError();
+ lastError = strprintf("CreateProcess failed for Tor binary '%s' with Windows error %lu", torBinaryPath.c_str(), err);
+ printf("ERROR: Failed to start Tor process (error %lu)\n", err);
return false;
}
@@ -329,6 +363,7 @@ bool CTorProcess::Start(const std::string& dataDir, int socks, int hsPort, bool
// Defensive check: Tor must not directly occupy the node's hidden-service
// virtual port. If it does, node startup will fail with a bind collision.
if (hiddenServiceEnabled && IsPortInUse(hiddenServicePort)) {
+ lastError = strprintf("Port %d is already busy while Tor hidden service is enabled. Another process is likely blocking the node listener.", hiddenServicePort);
printf("ERROR: Tor startup collision: hidden service port %d appears busy before node bind.\n",
hiddenServicePort);
printf(" Refusing to treat Tor as healthy because this would block the node listener.\n");
@@ -353,12 +388,20 @@ bool CTorProcess::Start(const std::string& dataDir, int socks, int hsPort, bool
// Check if Tor process is still alive
if (!IsRunning()) {
+ fs::path torLogPath = fs::path(torDataDir) / "tor.log";
+ std::string torLogTail = ReadTailLines(torLogPath, 8);
+ if (!torLogTail.empty()) {
+ lastError = strprintf("Tor process exited during bootstrap before the SOCKS port became ready. Recent tor.log: %s", torLogTail.c_str());
+ } else {
+ lastError = "Tor process exited during bootstrap before the SOCKS port became ready.";
+ }
printf("ERROR: Tor process exited prematurely\n");
running = false;
return false;
}
}
+ lastError = strprintf("Tor process started from '%s' but SOCKS port %d was not ready after 30 seconds.", torBinaryPath.c_str(), socksPort);
printf("WARNING: Tor started but SOCKS proxy not yet ready after 30s\n");
printf(" Tor may still be bootstrapping. .onion connections will work once ready.\n");
return true;
diff --git a/src/tor/tor_process.h b/src/tor/tor_process.h
index f183d8f..2784e09 100644
--- a/src/tor/tor_process.h
+++ b/src/tor/tor_process.h
@@ -19,6 +19,7 @@ private:
std::string torBinaryPath;
std::string torDataDir;
std::string torrcPath;
+ std::string lastError;
int socksPort;
int hiddenServicePort;
bool hiddenServiceEnabled;
@@ -59,6 +60,7 @@ public:
// Get the Tor binary path (for diagnostics)
std::string GetBinaryPath() const { return torBinaryPath; }
+ std::string GetLastError() const { return lastError; }
// Singleton access
static CTorProcess* GetInstance();