Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a4da39f23c | |||
| 87bfc15712 | |||
| 6353e9d5fa | |||
| 14ce8cc2a7 | |||
| d180b5870c | |||
| 596d4ab55c | |||
| 5af26f186e | |||
| 5530920b25 |
@@ -9,7 +9,7 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
VERSION: "5.1.6"
|
||||
VERSION: "5.1.9"
|
||||
|
||||
jobs:
|
||||
build-windows-qt:
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# Generated by qmake (3.1) (Qt 5.15.18)
|
||||
# Project: triangles-qt.pro
|
||||
# Template: app
|
||||
# Command: C:/msys64/mingw64/bin/qmake-qt5.exe -o Makefile triangles-qt.pro
|
||||
# Command: C:/msys64/mingw64/bin/qmake-qt5.exe -o Makefile triangles-qt.pro USE_QRCODE=1 USE_UPNP=-
|
||||
#############################################################################
|
||||
|
||||
MAKEFILE = Makefile
|
||||
@@ -156,7 +156,7 @@ Makefile: triangles-qt.pro C:/msys64/mingw64/share/qt5/mkspecs/win32-g++/qmake.c
|
||||
C:/msys64/mingw64/lib/qtmain.prl \
|
||||
C:/msys64/mingw64/share/qt5/mkspecs/features/build_pass.prf \
|
||||
src/qt/triangles.qrc
|
||||
$(QMAKE) -o Makefile triangles-qt.pro
|
||||
$(QMAKE) -o Makefile triangles-qt.pro USE_QRCODE=1 USE_UPNP=-
|
||||
C:/msys64/mingw64/share/qt5/mkspecs/features/spec_pre.prf:
|
||||
C:/msys64/mingw64/share/qt5/mkspecs/qdevice.pri:
|
||||
C:/msys64/mingw64/share/qt5/mkspecs/features/device_config.prf:
|
||||
@@ -244,7 +244,7 @@ C:/msys64/mingw64/lib/qtmain.prl:
|
||||
C:/msys64/mingw64/share/qt5/mkspecs/features/build_pass.prf:
|
||||
src/qt/triangles.qrc:
|
||||
qmake: FORCE
|
||||
@$(QMAKE) -o Makefile triangles-qt.pro
|
||||
@$(QMAKE) -o Makefile triangles-qt.pro USE_QRCODE=1 USE_UPNP=-
|
||||
|
||||
qmake_all: FORCE
|
||||
|
||||
|
||||
+1
-1
@@ -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 1
|
||||
#define CLIENT_VERSION_REVISION 6
|
||||
#define CLIENT_VERSION_REVISION 9
|
||||
#define CLIENT_VERSION_BUILD 0
|
||||
|
||||
// Converts the parameter X to a string after macro replacement on X has been performed.
|
||||
|
||||
+40
-3
@@ -12,6 +12,7 @@
|
||||
#include "checkpoints.h"
|
||||
#include "smessage.h"
|
||||
#include "tor/onion_v3.h"
|
||||
#include "tor/tor_process.h"
|
||||
#ifdef ENABLE_ZMQ
|
||||
#include "zmqpublishnotifier.h"
|
||||
#endif
|
||||
@@ -139,6 +140,7 @@ void Shutdown(void* parg)
|
||||
if (fFirstThread)
|
||||
{
|
||||
fShutdown = true;
|
||||
|
||||
int64_t nDeferredWaitStart = GetTimeMillis();
|
||||
while (true)
|
||||
{
|
||||
@@ -154,6 +156,7 @@ void Shutdown(void* parg)
|
||||
|
||||
SecureMsgShutdown();
|
||||
ShutdownTorV3();
|
||||
StopTorProcess();
|
||||
|
||||
#ifdef ENABLE_ZMQ
|
||||
if (pzmqNotifier)
|
||||
@@ -178,6 +181,7 @@ void Shutdown(void* parg)
|
||||
fs::remove(GetPidFile());
|
||||
UnregisterWallet(pwalletMain);
|
||||
delete pwalletMain;
|
||||
// DB is flushed and wallet saved - safe to force-exit if something hangs
|
||||
NewThread(ExitTimeout, NULL);
|
||||
MilliSleep(50);
|
||||
printf("Triangles exited\n\n");
|
||||
@@ -962,18 +966,33 @@ bool AppInit2()
|
||||
printf(" rescan %15"PRId64"ms\n", GetTimeMillis() - nStart);
|
||||
}
|
||||
|
||||
// ********************************************************* Step 8.5: initialize Tor V3 identity
|
||||
// ********************************************************* Step 8.5: start Tor and initialize V3 identity
|
||||
{
|
||||
uiInterface.InitMessage(_("Starting Tor..."));
|
||||
printf("Starting Tor process...\n");
|
||||
|
||||
// Start the Tor process (finds/launches tor binary, provides SOCKS proxy)
|
||||
std::string torDataPath = (GetDataDir() / "tor_data").string();
|
||||
bool torStarted = StartTorProcess(torDataPath);
|
||||
|
||||
if (torStarted) {
|
||||
printf("Tor process running, SOCKS proxy at %s\n",
|
||||
CTorProcess::GetInstance()->GetSocksProxy().c_str());
|
||||
} else {
|
||||
printf("WARNING: Tor not available. .onion peers will not be reachable.\n");
|
||||
printf(" Clearnet connections will still work normally.\n");
|
||||
}
|
||||
|
||||
// Initialize Tor V3 identity (Ed25519 keys, onion address)
|
||||
uiInterface.InitMessage(_("Initializing Tor V3 identity..."));
|
||||
printf("Initializing Tor V3 onion identity...\n");
|
||||
|
||||
// Tor V3 identity is innate to Triangles — always enabled
|
||||
LoadTorV3Config();
|
||||
TorV3Config& torConfig = GetTorV3Config();
|
||||
torConfig.enableTor = true;
|
||||
torConfig.enableHiddenService = true;
|
||||
torConfig.hiddenServicePort = GetListenPort();
|
||||
torConfig.torDataDirectory = (GetDataDir() / "tor_data").string();
|
||||
torConfig.torDataDirectory = torDataPath;
|
||||
|
||||
if (InitTorV3()) {
|
||||
string onionAddr = CTorV3Manager::GetInstance()->GetWalletOnionAddress();
|
||||
@@ -996,6 +1015,24 @@ bool AppInit2()
|
||||
} else {
|
||||
printf("WARNING: Failed to initialize Tor V3 identity\n");
|
||||
}
|
||||
|
||||
// Also check if Tor gave us a hidden service hostname
|
||||
if (torStarted) {
|
||||
fs::path torHsHostname = fs::path(torDataPath) / "hidden_service" / "hostname";
|
||||
if (fs::exists(torHsHostname)) {
|
||||
ifstream f(torHsHostname.string().c_str());
|
||||
string torOnion;
|
||||
if (f.is_open() && getline(f, torOnion)) {
|
||||
// Trim whitespace
|
||||
while (!torOnion.empty() && (torOnion.back() == '\n' || torOnion.back() == '\r' || torOnion.back() == ' '))
|
||||
torOnion.pop_back();
|
||||
if (!torOnion.empty()) {
|
||||
AddLocal(CService(torOnion, GetListenPort(), fNameLookup), LOCAL_MANUAL);
|
||||
printf("Tor hidden service (from Tor process): %s\n", torOnion.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ********************************************************* Step 9: import blocks
|
||||
|
||||
+118
-32
@@ -1639,6 +1639,12 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck)
|
||||
if (!CheckBlock(!fJustCheck, !fJustCheck, false))
|
||||
return false;
|
||||
|
||||
// Determine if this block is covered by the hardcoded checkpoint.
|
||||
// Below checkpoint: skip all input validation, FetchInputs, ConnectInputs,
|
||||
// and wallet sync. The checkpoint hash guarantees chain integrity for these blocks.
|
||||
bool fAssumeValid = (pindex->nHeight <= Checkpoints::GetTotalBlocksEstimate());
|
||||
bool fIsInitialDownload = IsInitialBlockDownload();
|
||||
|
||||
//// issue here: it doesn't know the version
|
||||
unsigned int nTxPos;
|
||||
if (fJustCheck)
|
||||
@@ -1658,6 +1664,20 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck)
|
||||
{
|
||||
uint256 hashTx = tx.GetHash();
|
||||
|
||||
CDiskTxPos posThisTx(pindex->nFile, pindex->nBlockPos, nTxPos);
|
||||
if (!fJustCheck)
|
||||
nTxPos += ::GetSerializeSize(tx, SER_DISK, CLIENT_VERSION);
|
||||
|
||||
// Fast path: below checkpoint, skip all input validation and spent-tracking.
|
||||
// Just record where each transaction lives on disk (txindex).
|
||||
if (fAssumeValid)
|
||||
{
|
||||
mapQueuedChanges[hashTx] = CTxIndex(posThisTx, tx.vout.size());
|
||||
continue;
|
||||
}
|
||||
|
||||
// Full validation path (above checkpoint)
|
||||
|
||||
// Do not allow blocks that contain transactions which 'overwrite' older transactions,
|
||||
// unless those are already completely spent.
|
||||
// If such overwrites are allowed, coinbases and transactions depending upon those
|
||||
@@ -1681,10 +1701,6 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck)
|
||||
if (nSigOps > MAX_BLOCK_SIGOPS)
|
||||
return DoS(100, error("ConnectBlock() : too many sigops"));
|
||||
|
||||
CDiskTxPos posThisTx(pindex->nFile, pindex->nBlockPos, nTxPos);
|
||||
if (!fJustCheck)
|
||||
nTxPos += ::GetSerializeSize(tx, SER_DISK, CLIENT_VERSION);
|
||||
|
||||
MapPrevTx mapInputs;
|
||||
if (tx.IsCoinBase())
|
||||
nValueOut += tx.GetValueOut();
|
||||
@@ -1717,20 +1733,18 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck)
|
||||
mapQueuedChanges[hashTx] = CTxIndex(posThisTx, tx.vout.size());
|
||||
}
|
||||
|
||||
if (IsProofOfWork() && pindex->nHeight > Checkpoints::GetTotalBlocksEstimate())
|
||||
if (!fAssumeValid)
|
||||
{
|
||||
int64_t nReward = GetProofOfWorkReward(nFees);
|
||||
// Check coinbase reward
|
||||
if (vtx[0].GetValueOut() > nReward)
|
||||
return DoS(50, error("ConnectBlock() : coinbase reward exceeded (actual=%"PRId64" vs calculated=%"PRId64")",
|
||||
vtx[0].GetValueOut(),
|
||||
nReward));
|
||||
}
|
||||
if (IsProofOfStake())
|
||||
if (IsProofOfWork())
|
||||
{
|
||||
// Skip expensive coin age calculation and reward validation for blocks
|
||||
// covered by the hardcoded checkpoint. The checkpoint guarantees chain integrity.
|
||||
if (pindex->nHeight > Checkpoints::GetTotalBlocksEstimate())
|
||||
int64_t nReward = GetProofOfWorkReward(nFees);
|
||||
// Check coinbase reward
|
||||
if (vtx[0].GetValueOut() > nReward)
|
||||
return DoS(50, error("ConnectBlock() : coinbase reward exceeded (actual=%"PRId64" vs calculated=%"PRId64")",
|
||||
vtx[0].GetValueOut(),
|
||||
nReward));
|
||||
}
|
||||
if (IsProofOfStake())
|
||||
{
|
||||
// triangles: coin stake tx earns reward instead of paying fee
|
||||
uint64_t nCoinAge;
|
||||
@@ -1760,8 +1774,8 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck)
|
||||
return error("ConnectBlock() : UpdateTxIndex failed");
|
||||
}
|
||||
|
||||
// Update address index
|
||||
if (fAddressIndex)
|
||||
// Update address index (skip during IBD - will be rebuilt on next start with -reindex)
|
||||
if (fAddressIndex && !fIsInitialDownload)
|
||||
{
|
||||
for (unsigned int i = 0; i < vtx.size(); i++)
|
||||
{
|
||||
@@ -1834,9 +1848,9 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck)
|
||||
return error("ConnectBlock() : WriteBlockIndex failed");
|
||||
}
|
||||
|
||||
// Watch for transactions paying to me
|
||||
// Skip during initial block download - wallet will rescan on next normal startup
|
||||
if (!IsInitialBlockDownload())
|
||||
// Skip wallet sync during IBD - a full wallet rescan runs when IBD completes.
|
||||
// This eliminates millions of per-transaction wallet lookups during sync.
|
||||
if (!fIsInitialDownload)
|
||||
{
|
||||
BOOST_FOREACH(CTransaction& tx, vtx)
|
||||
SyncWithWallets(tx, this, true);
|
||||
@@ -2038,7 +2052,8 @@ bool CBlock::SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew)
|
||||
}
|
||||
}
|
||||
|
||||
// Update best block in wallet (so we can detect restored wallets)
|
||||
// Update best block in wallet (so we can detect restored wallets).
|
||||
// During IBD, skip this so the wallet knows it needs rescanning on restart.
|
||||
bool fIsInitialDownload = IsInitialBlockDownload();
|
||||
if (!fIsInitialDownload)
|
||||
{
|
||||
@@ -2057,7 +2072,8 @@ bool CBlock::SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew)
|
||||
|
||||
uint256 nBestBlockTrust = pindexBest->nHeight != 0 ? (pindexBest->nChainTrust - pindexBest->pprev->nChainTrust) : pindexBest->nChainTrust;
|
||||
|
||||
if (nBestHeight % 10000 == 0 || nBestHeight > 2186900)
|
||||
// Log every 5000 blocks during sync, every block once caught up
|
||||
if (nBestHeight % 5000 == 0 || !IsInitialBlockDownload())
|
||||
printf("SetBestChain: new best=%s height=%d trust=%s blocktrust=%"PRId64" date=%s\n",
|
||||
hashBestChain.ToString().substr(0,20).c_str(), nBestHeight,
|
||||
CBigNum(nBestChainTrust).ToString().c_str(),
|
||||
@@ -2108,6 +2124,40 @@ bool CBlock::SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew)
|
||||
pNotificationQueue->Push(strBlockEvent);
|
||||
}
|
||||
|
||||
// Detect IBD-to-synced transition and trigger deferred work:
|
||||
// wallet rescan (since SyncWithWallets was skipped) and smsg chain scan.
|
||||
{
|
||||
static bool fWasInitialDownload = true;
|
||||
if (fWasInitialDownload && !fIsInitialDownload)
|
||||
{
|
||||
printf("*** Initial block download complete at height %d ***\n", nBestHeight);
|
||||
|
||||
// Update wallet best chain locator now that IBD is done
|
||||
const CBlockLocator locator(pindexBest);
|
||||
::SetBestChain(locator);
|
||||
|
||||
// Wallet rescan: SyncWithWallets was skipped during IBD, so scan
|
||||
// the entire chain to pick up all wallet transactions.
|
||||
if (pwalletMain)
|
||||
{
|
||||
printf("Starting post-IBD wallet rescan from genesis...\n");
|
||||
uiInterface.InitMessage(_("Rescanning wallet..."));
|
||||
int nFound = pwalletMain->ScanForWalletTransactions(pindexGenesisBlock, true);
|
||||
printf("Post-IBD wallet rescan complete: %d transactions found\n", nFound);
|
||||
}
|
||||
|
||||
// Secure messaging: scan chain for public keys needed to decrypt messages
|
||||
if (fSecMsgEnabled)
|
||||
{
|
||||
printf("Starting post-IBD secure message chain scan...\n");
|
||||
uiInterface.InitMessage(_("Scanning for secure messages..."));
|
||||
SecureMsgScanBlockChain();
|
||||
printf("Post-IBD secure message chain scan complete\n");
|
||||
}
|
||||
}
|
||||
fWasInitialDownload = fIsInitialDownload;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2604,8 +2654,8 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock)
|
||||
mapOrphanBlocksByPrev.erase(hashPrev);
|
||||
}
|
||||
|
||||
if (nBestHeight % 10000 == 0 || nBestHeight > 2186900)
|
||||
printf("ProcessBlock: ACCEPTED\n");
|
||||
if (nBestHeight % 5000 == 0 || !IsInitialBlockDownload())
|
||||
printf("ProcessBlock: ACCEPTED block %d\n", nBestHeight);
|
||||
|
||||
// triangles: if responsible for sync-checkpoint send it
|
||||
if (pfrom && !CSyncCheckpoint::strMasterPrivKey.empty())
|
||||
@@ -3217,13 +3267,14 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
|
||||
}
|
||||
}
|
||||
|
||||
// Ask the first connected node for block updates
|
||||
// Ask connected nodes for block updates
|
||||
// During IBD, always request blocks from any valid peer (critical for reconnection)
|
||||
static int nAskedForBlocks = 0;
|
||||
if (!pfrom->fClient && !pfrom->fOneShot &&
|
||||
(pfrom->nStartingHeight > (nBestHeight - 144)) &&
|
||||
(pfrom->nVersion < NOBLKS_VERSION_START ||
|
||||
pfrom->nVersion >= NOBLKS_VERSION_END) &&
|
||||
(nAskedForBlocks < 1 || vNodes.size() <= 1))
|
||||
(IsInitialBlockDownload() || nAskedForBlocks < 1 || vNodes.size() <= 1))
|
||||
{
|
||||
nAskedForBlocks++;
|
||||
pfrom->PushGetBlocks(pindexBest, uint256(0));
|
||||
@@ -3470,7 +3521,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
|
||||
// Send the rest of the chain
|
||||
if (pindex)
|
||||
pindex = pindex->pnext;
|
||||
int nLimit = 500;
|
||||
int nLimit = IsInitialBlockDownload() ? 20000 : 500;
|
||||
printf("getblocks %d to %s limit %d\n", (pindex ? pindex->nHeight : -1), hashStop.ToString().substr(0,20).c_str(), nLimit);
|
||||
for (; pindex; pindex = pindex->pnext)
|
||||
{
|
||||
@@ -3680,19 +3731,35 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
|
||||
vRecv >> block;
|
||||
uint256 hashBlock = block.GetHash();
|
||||
|
||||
printf("received block %s\n", hashBlock.ToString().substr(0,20).c_str());
|
||||
if (!IsInitialBlockDownload())
|
||||
printf("received block %s\n", hashBlock.ToString().substr(0,20).c_str());
|
||||
// block.print();
|
||||
|
||||
CInv inv(MSG_BLOCK, hashBlock);
|
||||
pfrom->AddInventoryKnown(inv);
|
||||
|
||||
if (ProcessBlock(pfrom, &block))
|
||||
{
|
||||
mapAlreadyAskedFor.erase(inv);
|
||||
|
||||
|
||||
// During IBD, proactively request more blocks to keep the pipeline full.
|
||||
// With assumevalid, blocks process near-instantly, so request aggressively.
|
||||
if (IsInitialBlockDownload())
|
||||
{
|
||||
static int nBlocksSinceRequest = 0;
|
||||
if (++nBlocksSinceRequest >= 100)
|
||||
{
|
||||
nBlocksSinceRequest = 0;
|
||||
pfrom->pindexLastGetBlocksBegin = NULL; // clear dedup filter
|
||||
pfrom->PushGetBlocks(pindexBest, uint256(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (block.nDoS)
|
||||
pfrom->Misbehaving(block.nDoS);
|
||||
|
||||
if (fSecMsgEnabled)
|
||||
|
||||
if (fSecMsgEnabled && !IsInitialBlockDownload())
|
||||
SecureMsgScanBlock(block);
|
||||
}
|
||||
|
||||
@@ -4126,6 +4193,25 @@ bool SendMessages(CNode* pto, bool fSendTrickle)
|
||||
pto->PushMessage("inv", vInv);
|
||||
|
||||
|
||||
//
|
||||
// Stall detection: if IBD and no new blocks for 30 seconds, re-request
|
||||
//
|
||||
if (IsInitialBlockDownload() && !pto->fClient)
|
||||
{
|
||||
static int64_t nLastBlockReceived = 0;
|
||||
static int nLastHeight = 0;
|
||||
if (nBestHeight > nLastHeight) {
|
||||
nLastHeight = nBestHeight;
|
||||
nLastBlockReceived = GetTime();
|
||||
} else if (nLastBlockReceived > 0 && GetTime() - nLastBlockReceived > 5) {
|
||||
printf("IBD stall detected at height %d, re-requesting blocks\n", nBestHeight);
|
||||
// Clear dedup filter so the request actually sends
|
||||
pto->pindexLastGetBlocksBegin = NULL;
|
||||
pto->PushGetBlocks(pindexBest, uint256(0));
|
||||
nLastBlockReceived = GetTime(); // reset timer
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Message: getdata
|
||||
//
|
||||
|
||||
+9
-1
@@ -135,7 +135,8 @@ OBJS= \
|
||||
obj/scrypt-x86.o \
|
||||
obj/scrypt-x86_64.o \
|
||||
obj/smessage.o \
|
||||
obj/onion_v3.o
|
||||
obj/onion_v3.o \
|
||||
obj/tor_process.o
|
||||
|
||||
all: trianglesd.exe
|
||||
|
||||
@@ -200,6 +201,13 @@ obj/onion_v3.o: tor/onion_v3.cpp
|
||||
-e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \
|
||||
rm -f $(@:%.o=%.d)
|
||||
|
||||
obj/tor_process.o: tor/tor_process.cpp
|
||||
$(CXX) -c $(xCXXFLAGS) -MMD -MF $(@:%.o=%.d) -o $@ $<
|
||||
@cp $(@:%.o=%.d) $(@:%.o=%.P); \
|
||||
sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \
|
||||
-e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \
|
||||
rm -f $(@:%.o=%.d)
|
||||
|
||||
obj/net_bootstrap.o: net_bootstrap.cpp
|
||||
$(CXX) -c $(xCXXFLAGS) -MMD -MF $(@:%.o=%.d) -o $@ $<
|
||||
@cp $(@:%.o=%.d) $(@:%.o=%.P); \
|
||||
|
||||
+9
-1
@@ -170,7 +170,8 @@ OBJS= \
|
||||
obj/scrypt-x86.o \
|
||||
obj/scrypt-x86_64.o \
|
||||
obj/smessage.o \
|
||||
obj/onion_v3.o
|
||||
obj/onion_v3.o \
|
||||
obj/tor_process.o
|
||||
|
||||
# ZMQ support (optional)
|
||||
# Build with: make -f makefile.unix USE_ZMQ=1
|
||||
@@ -246,6 +247,13 @@ obj/onion_v3.o: tor/onion_v3.cpp
|
||||
-e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \
|
||||
rm -f $(@:%.o=%.d)
|
||||
|
||||
obj/tor_process.o: tor/tor_process.cpp
|
||||
$(CXX) -c $(xCXXFLAGS) -MMD -MF $(@:%.o=%.d) -o $@ $<
|
||||
@cp $(@:%.o=%.d) $(@:%.o=%.P); \
|
||||
sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \
|
||||
-e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \
|
||||
rm -f $(@:%.o=%.d)
|
||||
|
||||
obj/net_bootstrap.o: net_bootstrap.cpp
|
||||
$(CXX) -c $(xCXXFLAGS) -MMD -MF $(@:%.o=%.d) -o $@ $<
|
||||
@cp $(@:%.o=%.d) $(@:%.o=%.P); \
|
||||
|
||||
+51
-23
@@ -6,6 +6,7 @@
|
||||
#include "irc.h"
|
||||
#include "db.h"
|
||||
#include "net.h"
|
||||
#include "main.h"
|
||||
#include "init.h"
|
||||
#include "strlcpy.h"
|
||||
#include "addrman.h"
|
||||
@@ -889,7 +890,7 @@ void ThreadSocketHandler2(void* parg)
|
||||
//
|
||||
struct timeval timeout;
|
||||
timeout.tv_sec = 0;
|
||||
timeout.tv_usec = 50000; // frequency to poll pnode->vSend
|
||||
timeout.tv_usec = IsInitialBlockDownload() ? 10000 : 50000; // faster polling during IBD
|
||||
|
||||
fd_set fdsetRecv;
|
||||
fd_set fdsetSend;
|
||||
@@ -1574,24 +1575,29 @@ void ThreadOpenConnections2(void* parg)
|
||||
if (fShutdown)
|
||||
return;
|
||||
|
||||
// Add seed nodes if IRC isn't working
|
||||
if (addrman.size()==0 && (GetTime() - nStart > 60) && !fTestNet)
|
||||
// Add hardcoded seed nodes when we have no connections.
|
||||
// Original check (addrman.size()==0) was too conservative - stale entries
|
||||
// in peers.dat would prevent fallback to working hardcoded IPs forever.
|
||||
{
|
||||
std::vector<CAddress> vAdd;
|
||||
for (unsigned int i = 0; i < ARRAYLEN(pnSeed); i++)
|
||||
{
|
||||
// It'll only connect to one or two seed nodes because once it connects,
|
||||
// it'll get a pile of addresses with newer timestamps.
|
||||
// Seed nodes are given a random 'last seen time' of between one and two
|
||||
// weeks ago.
|
||||
const int64_t nOneWeek = 7*24*60*60;
|
||||
struct in_addr ip;
|
||||
memcpy(&ip, &pnSeed[i], sizeof(ip));
|
||||
CAddress addr(CService(ip, GetDefaultPort()));
|
||||
addr.nTime = GetTime()-GetRand(nOneWeek)-nOneWeek;
|
||||
vAdd.push_back(addr);
|
||||
LOCK(cs_vNodes);
|
||||
bool fNoOutbound = true;
|
||||
BOOST_FOREACH(CNode* pnode, vNodes) {
|
||||
if (!pnode->fInbound) { fNoOutbound = false; break; }
|
||||
}
|
||||
if (fNoOutbound && (GetTime() - nStart > 30) && !fTestNet)
|
||||
{
|
||||
std::vector<CAddress> vAdd;
|
||||
for (unsigned int i = 0; i < ARRAYLEN(pnSeed); i++)
|
||||
{
|
||||
struct in_addr ip;
|
||||
memcpy(&ip, &pnSeed[i], sizeof(ip));
|
||||
CAddress addr(CService(ip, GetDefaultPort()));
|
||||
addr.nTime = GetTime() - GetRand(60*60); // seen recently
|
||||
vAdd.push_back(addr);
|
||||
}
|
||||
addrman.Add(vAdd, CNetAddr("127.0.0.1"));
|
||||
printf("No outbound connections after 30s, added %d hardcoded seeds\n", (int)vAdd.size());
|
||||
}
|
||||
addrman.Add(vAdd, CNetAddr("127.0.0.1"));
|
||||
}
|
||||
|
||||
//
|
||||
@@ -1809,6 +1815,7 @@ void ThreadMessageHandler2(void* parg)
|
||||
{
|
||||
printf("ThreadMessageHandler started\n");
|
||||
SetThreadPriority(THREAD_PRIORITY_BELOW_NORMAL);
|
||||
bool fWasBoosted = false;
|
||||
while (!fShutdown)
|
||||
{
|
||||
vector<CNode*> vNodesCopy;
|
||||
@@ -1851,11 +1858,21 @@ void ThreadMessageHandler2(void* parg)
|
||||
pnode->Release();
|
||||
}
|
||||
|
||||
// Boost thread priority during IBD, restore when caught up
|
||||
if (IsInitialBlockDownload() && !fWasBoosted) {
|
||||
SetThreadPriority(THREAD_PRIORITY_NORMAL);
|
||||
fWasBoosted = true;
|
||||
} else if (!IsInitialBlockDownload() && fWasBoosted) {
|
||||
SetThreadPriority(THREAD_PRIORITY_BELOW_NORMAL);
|
||||
fWasBoosted = false;
|
||||
}
|
||||
|
||||
// Wait and allow messages to bunch up.
|
||||
// During IBD, use a shorter sleep to maximize block processing throughput.
|
||||
// Reduce vnThreadsRunning so StopNode has permission to exit while
|
||||
// we're sleeping, but we must always check fShutdown after doing this.
|
||||
vnThreadsRunning[THREAD_MESSAGEHANDLER]--;
|
||||
MilliSleep(100);
|
||||
MilliSleep(IsInitialBlockDownload() ? 10 : 100);
|
||||
if (fRequestShutdown)
|
||||
StartShutdown();
|
||||
vnThreadsRunning[THREAD_MESSAGEHANDLER]++;
|
||||
@@ -2006,9 +2023,10 @@ void static Discover()
|
||||
}
|
||||
|
||||
static void run_tor() {
|
||||
// Old embedded Tor v2 client removed - incompatible with OpenSSL 3.x.
|
||||
// Tor v3 onion services are handled by onion_v3.cpp via external Tor/SOCKS5.
|
||||
printf("Tor v3 mode: using external Tor process via SOCKS5 proxy.\n");
|
||||
// Tor process is now managed by CTorProcess (tor_process.cpp)
|
||||
// which starts an external Tor binary with SOCKS5 proxy and v3 hidden service.
|
||||
// The old embedded Tor v2 code was removed (incompatible with OpenSSL 3.x).
|
||||
printf("Tor v3 mode: using managed Tor process via SOCKS5 proxy.\n");
|
||||
set_initialized();
|
||||
}
|
||||
|
||||
@@ -2148,8 +2166,18 @@ bool StopNode()
|
||||
if (vnThreadsRunning[THREAD_ADDEDCONNECTIONS] > 0) printf("ThreadOpenAddedConnections still running\n");
|
||||
if (vnThreadsRunning[THREAD_DUMPADDRESS] > 0) printf("ThreadDumpAddresses still running\n");
|
||||
if (vnThreadsRunning[THREAD_STAKE_MINER] > 0) printf("ThreadStakeMiner still running\n");
|
||||
while (vnThreadsRunning[THREAD_MESSAGEHANDLER] > 0 || vnThreadsRunning[THREAD_RPCHANDLER] > 0)
|
||||
MilliSleep(20);
|
||||
{
|
||||
int64_t nWaitStart = GetTime();
|
||||
while (vnThreadsRunning[THREAD_MESSAGEHANDLER] > 0 || vnThreadsRunning[THREAD_RPCHANDLER] > 0)
|
||||
{
|
||||
if (GetTime() - nWaitStart > 10)
|
||||
{
|
||||
printf("Timed out waiting for message/RPC threads to stop\n");
|
||||
break;
|
||||
}
|
||||
MilliSleep(20);
|
||||
}
|
||||
}
|
||||
MilliSleep(50);
|
||||
DumpAddresses();
|
||||
return true;
|
||||
|
||||
@@ -22,12 +22,13 @@
|
||||
class CRequestTracker;
|
||||
class CNode;
|
||||
class CBlockIndex;
|
||||
bool IsInitialBlockDownload();
|
||||
extern int nBestHeight;
|
||||
|
||||
|
||||
|
||||
inline unsigned int ReceiveFloodSize() { return 1000*GetArg("-maxreceivebuffer", 5*1000); }
|
||||
inline unsigned int SendBufferSize() { return 1000*GetArg("-maxsendbuffer", 1*1000); }
|
||||
inline unsigned int ReceiveFloodSize() { return (unsigned int)-1; }
|
||||
inline unsigned int SendBufferSize() { return (unsigned int)-1; }
|
||||
|
||||
void AddOneShot(std::string strDest);
|
||||
bool RecvLine(SOCKET hSocket, std::string& strLine);
|
||||
@@ -429,8 +430,12 @@ public:
|
||||
nNow = std::max(nNow, nLastTime);
|
||||
nLastTime = nNow;
|
||||
|
||||
// Each retry is 2 minutes after the last
|
||||
nRequestTime = std::max(nRequestTime + 2 * 60 * 1000000, nNow);
|
||||
// During IBD, request immediately (no 2-minute retry delay)
|
||||
// Normal operation: each retry is 2 minutes after the last
|
||||
if (nRequestTime > 0 && IsInitialBlockDownload())
|
||||
nRequestTime = nNow;
|
||||
else
|
||||
nRequestTime = std::max(nRequestTime + 2 * 60 * 1000000, nNow);
|
||||
mapAskFor.insert(std::make_pair(nRequestTime, inv));
|
||||
}
|
||||
|
||||
|
||||
@@ -63,7 +63,9 @@ void ClientModel::updateTimer()
|
||||
int newNumBlocks = getNumBlocks();
|
||||
int newNumBlocksOfPeers = getNumBlocksOfPeers();
|
||||
|
||||
if(cachedNumBlocks != newNumBlocks || cachedNumBlocksOfPeers != newNumBlocksOfPeers)
|
||||
// Always emit during IBD so the speed/ETA display stays live
|
||||
if(cachedNumBlocks != newNumBlocks || cachedNumBlocksOfPeers != newNumBlocksOfPeers
|
||||
|| newNumBlocks < newNumBlocksOfPeers)
|
||||
{
|
||||
cachedNumBlocks = newNumBlocks;
|
||||
cachedNumBlocksOfPeers = newNumBlocksOfPeers;
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
#include "introdialog.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <QSettings>
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QFileDialog>
|
||||
#include <QDir>
|
||||
#include <QMessageBox>
|
||||
#include <QDialogButtonBox>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
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();
|
||||
boost::filesystem::path fsPath(path.toStdString());
|
||||
|
||||
// Walk up to find an existing parent
|
||||
try {
|
||||
while (!fsPath.empty() && !boost::filesystem::exists(fsPath))
|
||||
fsPath = fsPath.parent_path();
|
||||
|
||||
if (!fsPath.empty()) {
|
||||
boost::filesystem::space_info si = boost::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 boost::filesystem::filesystem_error &) {
|
||||
freeSpaceLabel->setText(tr("Cannot determine free space"));
|
||||
}
|
||||
}
|
||||
|
||||
bool IntroDialog::pickDataDirectory()
|
||||
{
|
||||
namespace fs = boost::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);
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
#ifndef INTRODIALOG_H
|
||||
#define INTRODIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QRadioButton>
|
||||
#include <QPushButton>
|
||||
|
||||
/** Data directory selection dialog shown on first run. */
|
||||
class IntroDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit IntroDialog(QWidget *parent = 0);
|
||||
|
||||
QString getDataDirectory() const;
|
||||
void setDataDirectory(const QString &dir);
|
||||
|
||||
/**
|
||||
* Check settings or show the dialog to choose data directory.
|
||||
* Returns true if a directory was selected, false if the user cancelled.
|
||||
* Sets mapArgs["-datadir"] if a non-default directory was chosen.
|
||||
*/
|
||||
static bool pickDataDirectory();
|
||||
|
||||
private slots:
|
||||
void on_browseButton_clicked();
|
||||
void on_defaultRadio_toggled(bool checked);
|
||||
void updateFreeSpace();
|
||||
|
||||
private:
|
||||
QRadioButton *defaultRadio;
|
||||
QRadioButton *customRadio;
|
||||
QLineEdit *pathEdit;
|
||||
QPushButton *browseButton;
|
||||
QLabel *freeSpaceLabel;
|
||||
QString defaultDataDir;
|
||||
};
|
||||
|
||||
#endif // INTRODIALOG_H
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "guiutil.h"
|
||||
#include "guiconstants.h"
|
||||
|
||||
#include "introdialog.h"
|
||||
#include "init.h"
|
||||
#include "ui_interface.h"
|
||||
#include "qtipcserver.h"
|
||||
@@ -131,6 +132,10 @@ int main(int argc, char *argv[])
|
||||
// Command-line options take precedence:
|
||||
ParseParameters(argc, argv);
|
||||
|
||||
// Show data directory selection dialog on first run (unless -datadir was passed)
|
||||
if (!IntroDialog::pickDataDirectory())
|
||||
return 0;
|
||||
|
||||
// ... then triangles.conf:
|
||||
if (!boost::filesystem::is_directory(GetDataDir(false)))
|
||||
{
|
||||
|
||||
+57
-13
@@ -751,24 +751,57 @@ void TrianglesGUI::setNumBlocks(int count, int nTotalBlocks)
|
||||
|
||||
QString tooltip;
|
||||
|
||||
QString importText;
|
||||
importText = tr("Synchronizing with network...");
|
||||
|
||||
if(count < nTotalBlocks)
|
||||
{
|
||||
// Calculate blocks/sec - only update rate when new blocks arrive
|
||||
static int lastCount = 0;
|
||||
static qint64 lastRateTime = 0;
|
||||
static float blocksPerSec = 0.0f;
|
||||
qint64 now = QDateTime::currentMSecsSinceEpoch();
|
||||
if (count > lastCount) {
|
||||
// New blocks arrived - recalculate speed
|
||||
if (lastRateTime > 0) {
|
||||
float elapsed = (now - lastRateTime) / 1000.0f;
|
||||
if (elapsed > 0.1f) {
|
||||
float instantRate = (count - lastCount) / elapsed;
|
||||
blocksPerSec = (blocksPerSec < 0.1f) ? instantRate : (blocksPerSec * 0.7f + instantRate * 0.3f);
|
||||
}
|
||||
}
|
||||
lastCount = count;
|
||||
lastRateTime = now;
|
||||
} else if (lastRateTime > 0 && (now - lastRateTime) > 10000) {
|
||||
// No blocks for 10+ seconds - show 0
|
||||
blocksPerSec = 0.0f;
|
||||
}
|
||||
|
||||
int nRemainingBlocks = nTotalBlocks - count;
|
||||
float nPercentageDone = count / (nTotalBlocks * 0.01f);
|
||||
|
||||
progressBarLabel->setText(importText);
|
||||
// Build informative status text
|
||||
QString speedText;
|
||||
if (blocksPerSec >= 1.0f) {
|
||||
int etaSeconds = (int)(nRemainingBlocks / blocksPerSec);
|
||||
QString etaStr;
|
||||
if (etaSeconds < 60)
|
||||
etaStr = tr("%n sec", "", etaSeconds);
|
||||
else if (etaSeconds < 3600)
|
||||
etaStr = tr("%n min", "", etaSeconds / 60);
|
||||
else
|
||||
etaStr = tr("%1h %2m").arg(etaSeconds / 3600).arg((etaSeconds % 3600) / 60);
|
||||
speedText = tr("Syncing: %1 blk/s ~%2 remaining").arg(blocksPerSec, 0, 'f', 1).arg(etaStr);
|
||||
} else {
|
||||
speedText = tr("Synchronizing with network...");
|
||||
}
|
||||
|
||||
progressBarLabel->setText(speedText);
|
||||
progressBarLabel->setVisible(true);
|
||||
progressBar->setFormat(tr("~%n block(s) remaining", "", nRemainingBlocks));
|
||||
progressBar->setFormat(tr("Block %1 / %2 (%3%)").arg(count).arg(nTotalBlocks).arg(nPercentageDone, 0, 'f', 2));
|
||||
progressBar->setMaximum(nTotalBlocks);
|
||||
progressBar->setValue(count);
|
||||
progressBar->setVisible(true);
|
||||
ui->label_blocks->setText(tr("%n blocks", "", count));
|
||||
ui->label_blocks->setVisible(true);
|
||||
ui->label_blocks->setVisible(false);
|
||||
|
||||
tooltip = tr("Downloaded %1 of %2 blocks of transaction history (%3% done).").arg(count).arg(nTotalBlocks).arg(nPercentageDone, 0, 'f', 2);
|
||||
tooltip = tr("Downloaded %1 of %2 blocks of transaction history (%3% done).").arg(count).arg(nTotalBlocks).arg(nPercentageDone, 0, 'f', 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -871,16 +904,27 @@ void TrianglesGUI::changeEvent(QEvent *e)
|
||||
|
||||
void TrianglesGUI::closeEvent(QCloseEvent *event)
|
||||
{
|
||||
#ifndef Q_OS_MAC // Ignored on Mac
|
||||
if(clientModel)
|
||||
{
|
||||
#ifndef Q_OS_MAC // Ignored on Mac
|
||||
if(!clientModel->getOptionsModel()->getMinimizeToTray() &&
|
||||
!clientModel->getOptionsModel()->getMinimizeOnClose())
|
||||
if(clientModel->getOptionsModel()->getMinimizeOnClose())
|
||||
{
|
||||
QApplication::quit();
|
||||
// Minimize to taskbar instead of closing
|
||||
QMainWindow::showMinimized();
|
||||
event->ignore();
|
||||
return;
|
||||
}
|
||||
if(clientModel->getOptionsModel()->getMinimizeToTray() && trayIcon)
|
||||
{
|
||||
// Hide to system tray instead of closing
|
||||
hide();
|
||||
event->ignore();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
// Actually closing - quit the application
|
||||
QApplication::quit();
|
||||
QMainWindow::closeEvent(event);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,413 @@
|
||||
// Copyright (c) 2024-2025 Triangles developers
|
||||
// Tor Process Manager - launches and manages an external Tor binary
|
||||
// Distributed under the MIT/X11 software license
|
||||
|
||||
#ifdef WIN32
|
||||
#define NOMINMAX
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#ifndef _WIN32_WINNT
|
||||
#define _WIN32_WINNT 0x0600
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include "tor_process.h"
|
||||
#include "../util.h"
|
||||
#include "../net.h"
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <fstream>
|
||||
#include <cstdio>
|
||||
|
||||
#ifdef WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include <tlhelp32.h>
|
||||
#else
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
static CTorProcess* torProcessInstance = nullptr;
|
||||
|
||||
CTorProcess* CTorProcess::GetInstance()
|
||||
{
|
||||
if (!torProcessInstance) {
|
||||
torProcessInstance = new CTorProcess();
|
||||
}
|
||||
return torProcessInstance;
|
||||
}
|
||||
|
||||
CTorProcess::CTorProcess()
|
||||
: socksPort(19099)
|
||||
, hiddenServicePort(24112)
|
||||
, running(false)
|
||||
#ifdef WIN32
|
||||
, hProcess(NULL)
|
||||
, processId(0)
|
||||
#else
|
||||
, processId(0)
|
||||
#endif
|
||||
{
|
||||
}
|
||||
|
||||
CTorProcess::~CTorProcess()
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
std::string CTorProcess::FindTorBinary()
|
||||
{
|
||||
// List of candidate paths to search for the Tor binary
|
||||
std::vector<std::string> candidates;
|
||||
|
||||
#ifdef WIN32
|
||||
// Same directory as the wallet executable
|
||||
char exePath[MAX_PATH];
|
||||
if (GetModuleFileNameA(NULL, exePath, MAX_PATH)) {
|
||||
fs::path exeDir = fs::path(exePath).parent_path();
|
||||
candidates.push_back((exeDir / "tor.exe").string());
|
||||
candidates.push_back((exeDir / "tor" / "tor.exe").string());
|
||||
candidates.push_back((exeDir / "Tor" / "tor.exe").string());
|
||||
}
|
||||
|
||||
// Data directory
|
||||
candidates.push_back((GetDataDir() / "tor.exe").string());
|
||||
candidates.push_back((GetDataDir() / "tor" / "tor.exe").string());
|
||||
|
||||
// Common Windows install locations
|
||||
const char* programFiles = getenv("ProgramFiles");
|
||||
if (programFiles) {
|
||||
candidates.push_back(std::string(programFiles) + "\\Tor\\tor.exe");
|
||||
candidates.push_back(std::string(programFiles) + "\\Tor Browser\\Browser\\TorBrowser\\Tor\\tor.exe");
|
||||
}
|
||||
const char* programFilesX86 = getenv("ProgramFiles(x86)");
|
||||
if (programFilesX86) {
|
||||
candidates.push_back(std::string(programFilesX86) + "\\Tor\\tor.exe");
|
||||
candidates.push_back(std::string(programFilesX86) + "\\Tor Browser\\Browser\\TorBrowser\\Tor\\tor.exe");
|
||||
}
|
||||
const char* localAppData = getenv("LOCALAPPDATA");
|
||||
if (localAppData) {
|
||||
candidates.push_back(std::string(localAppData) + "\\Tor Browser\\Browser\\TorBrowser\\Tor\\tor.exe");
|
||||
}
|
||||
|
||||
// Tor Expert Bundle (common install)
|
||||
candidates.push_back("C:\\Tor\\tor.exe");
|
||||
|
||||
#else
|
||||
// Same directory as the wallet executable
|
||||
char exePath[4096];
|
||||
ssize_t len = readlink("/proc/self/exe", exePath, sizeof(exePath) - 1);
|
||||
if (len > 0) {
|
||||
exePath[len] = '\0';
|
||||
fs::path exeDir = fs::path(exePath).parent_path();
|
||||
candidates.push_back((exeDir / "tor").string());
|
||||
candidates.push_back((exeDir / "tor" / "tor").string());
|
||||
}
|
||||
|
||||
// Data directory
|
||||
candidates.push_back((GetDataDir() / "tor").string());
|
||||
candidates.push_back((GetDataDir() / "tor" / "tor").string());
|
||||
|
||||
// Standard Linux/macOS locations
|
||||
candidates.push_back("/usr/bin/tor");
|
||||
candidates.push_back("/usr/local/bin/tor");
|
||||
candidates.push_back("/usr/sbin/tor");
|
||||
candidates.push_back("/opt/tor/bin/tor");
|
||||
candidates.push_back("/snap/bin/tor");
|
||||
|
||||
// Homebrew (macOS)
|
||||
candidates.push_back("/opt/homebrew/bin/tor");
|
||||
candidates.push_back("/usr/local/opt/tor/bin/tor");
|
||||
#endif
|
||||
|
||||
// Check each candidate
|
||||
for (const std::string& path : candidates) {
|
||||
if (fs::exists(path)) {
|
||||
printf("Found Tor binary at: %s\n", path.c_str());
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
bool CTorProcess::IsPortInUse(int port)
|
||||
{
|
||||
#ifdef WIN32
|
||||
SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
if (sock == INVALID_SOCKET) return false;
|
||||
|
||||
struct sockaddr_in addr;
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
|
||||
addr.sin_port = htons(port);
|
||||
|
||||
int result = connect(sock, (struct sockaddr*)&addr, sizeof(addr));
|
||||
closesocket(sock);
|
||||
return (result == 0);
|
||||
#else
|
||||
int sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (sock < 0) return false;
|
||||
|
||||
struct sockaddr_in addr;
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
|
||||
addr.sin_port = htons(port);
|
||||
|
||||
int result = connect(sock, (struct sockaddr*)&addr, sizeof(addr));
|
||||
close(sock);
|
||||
return (result == 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool CTorProcess::WriteTorrc()
|
||||
{
|
||||
fs::path dataPath(torDataDir);
|
||||
fs::create_directories(dataPath);
|
||||
|
||||
torrcPath = (dataPath / "torrc").string();
|
||||
|
||||
// Hidden service directory
|
||||
fs::path hsDir = dataPath / "hidden_service";
|
||||
fs::create_directories(hsDir);
|
||||
|
||||
std::ofstream torrc(torrcPath.c_str());
|
||||
if (!torrc.is_open()) {
|
||||
printf("ERROR: Cannot write torrc to %s\n", torrcPath.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
torrc << "# Triangles Wallet Tor Configuration (auto-generated)\n";
|
||||
torrc << "# Do not edit - this file is overwritten on startup\n\n";
|
||||
|
||||
// SOCKS proxy for wallet connections
|
||||
torrc << "SocksPort " << socksPort << "\n";
|
||||
|
||||
// Data directory for Tor state
|
||||
fs::path torStateDir = dataPath / "state";
|
||||
fs::create_directories(torStateDir);
|
||||
torrc << "DataDirectory " << torStateDir.string() << "\n";
|
||||
|
||||
// V3 hidden service so this node is reachable via .onion
|
||||
torrc << "HiddenServiceDir " << hsDir.string() << "\n";
|
||||
torrc << "HiddenServiceVersion 3\n";
|
||||
torrc << "HiddenServicePort " << hiddenServicePort
|
||||
<< " 127.0.0.1:" << hiddenServicePort << "\n";
|
||||
|
||||
// Reduce bandwidth/resource usage for wallet use
|
||||
torrc << "ClientOnly 1\n";
|
||||
|
||||
// Disable unused features
|
||||
torrc << "AvoidDiskWrites 1\n";
|
||||
torrc << "Log notice stderr\n";
|
||||
|
||||
torrc.close();
|
||||
|
||||
printf("Wrote torrc to %s (SOCKS %d, HS port %d)\n",
|
||||
torrcPath.c_str(), socksPort, hiddenServicePort);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CTorProcess::Start(const std::string& dataDir, int socks, int hsPort)
|
||||
{
|
||||
socksPort = socks;
|
||||
hiddenServicePort = hsPort;
|
||||
torDataDir = dataDir;
|
||||
|
||||
// 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);
|
||||
running = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Find Tor binary
|
||||
torBinaryPath = FindTorBinary();
|
||||
if (torBinaryPath.empty()) {
|
||||
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");
|
||||
printf(" Place tor executable next to the wallet binary for auto-detection.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write configuration
|
||||
if (!WriteTorrc()) {
|
||||
printf("ERROR: Failed to write Tor configuration\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
printf("Starting Tor process: %s -f %s\n", torBinaryPath.c_str(), torrcPath.c_str());
|
||||
|
||||
#ifdef WIN32
|
||||
STARTUPINFOA si;
|
||||
PROCESS_INFORMATION pi;
|
||||
ZeroMemory(&si, sizeof(si));
|
||||
si.cb = sizeof(si);
|
||||
si.dwFlags = STARTF_USESHOWWINDOW;
|
||||
si.wShowWindow = SW_HIDE; // Run hidden
|
||||
ZeroMemory(&pi, sizeof(pi));
|
||||
|
||||
std::string cmdLine = "\"" + torBinaryPath + "\" -f \"" + torrcPath + "\"";
|
||||
|
||||
if (!CreateProcessA(
|
||||
NULL,
|
||||
(LPSTR)cmdLine.c_str(),
|
||||
NULL, NULL,
|
||||
FALSE,
|
||||
CREATE_NO_WINDOW,
|
||||
NULL, NULL,
|
||||
&si, &pi))
|
||||
{
|
||||
printf("ERROR: Failed to start Tor process (error %lu)\n", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
hProcess = pi.hProcess;
|
||||
processId = pi.dwProcessId;
|
||||
CloseHandle(pi.hThread);
|
||||
|
||||
printf("Tor process started (PID %lu)\n", processId);
|
||||
#else
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) {
|
||||
printf("ERROR: Failed to fork for Tor process\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pid == 0) {
|
||||
// Child process - exec Tor
|
||||
// Redirect stdout/stderr to /dev/null to avoid cluttering wallet output
|
||||
freopen("/dev/null", "w", stdout);
|
||||
freopen("/dev/null", "w", stderr);
|
||||
execl(torBinaryPath.c_str(), torBinaryPath.c_str(),
|
||||
"-f", torrcPath.c_str(), (char*)NULL);
|
||||
// If exec fails, exit child
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
processId = pid;
|
||||
printf("Tor process started (PID %d)\n", processId);
|
||||
#endif
|
||||
|
||||
running = true;
|
||||
|
||||
// Wait a few seconds for Tor to bootstrap, then check if SOCKS is up
|
||||
printf("Waiting for Tor to bootstrap...\n");
|
||||
for (int i = 0; i < 30; i++) {
|
||||
MilliSleep(1000);
|
||||
if (fShutdown) {
|
||||
Stop();
|
||||
return false;
|
||||
}
|
||||
if (IsPortInUse(socksPort)) {
|
||||
printf("Tor SOCKS proxy ready on port %d (took %ds)\n", socksPort, i + 1);
|
||||
|
||||
// Read and display the hidden service hostname if available
|
||||
fs::path hsHostname = fs::path(torDataDir) / "hidden_service" / "hostname";
|
||||
if (fs::exists(hsHostname)) {
|
||||
std::ifstream f(hsHostname.string().c_str());
|
||||
std::string hostname;
|
||||
if (f.is_open() && std::getline(f, hostname)) {
|
||||
printf("Tor hidden service: %s\n", hostname.c_str());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if Tor process is still alive
|
||||
if (!IsRunning()) {
|
||||
printf("ERROR: Tor process exited prematurely\n");
|
||||
running = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void CTorProcess::Stop()
|
||||
{
|
||||
if (!running) return;
|
||||
|
||||
#ifdef WIN32
|
||||
if (hProcess != NULL) {
|
||||
printf("Stopping Tor process (PID %lu)...\n", processId);
|
||||
TerminateProcess(hProcess, 0);
|
||||
WaitForSingleObject(hProcess, 5000);
|
||||
CloseHandle(hProcess);
|
||||
hProcess = NULL;
|
||||
}
|
||||
#else
|
||||
if (processId > 0) {
|
||||
printf("Stopping Tor process (PID %d)...\n", processId);
|
||||
kill(processId, SIGTERM);
|
||||
// Wait up to 5 seconds for graceful shutdown
|
||||
for (int i = 0; i < 50; i++) {
|
||||
int status;
|
||||
pid_t result = waitpid(processId, &status, WNOHANG);
|
||||
if (result != 0) break;
|
||||
MilliSleep(100);
|
||||
}
|
||||
// Force kill if still running
|
||||
kill(processId, SIGKILL);
|
||||
waitpid(processId, NULL, 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
processId = 0;
|
||||
running = false;
|
||||
printf("Tor process stopped\n");
|
||||
}
|
||||
|
||||
bool CTorProcess::IsRunning()
|
||||
{
|
||||
if (!running) return false;
|
||||
|
||||
#ifdef WIN32
|
||||
if (hProcess == NULL) return false;
|
||||
DWORD exitCode;
|
||||
if (GetExitCodeProcess(hProcess, &exitCode)) {
|
||||
return (exitCode == STILL_ACTIVE);
|
||||
}
|
||||
return false;
|
||||
#else
|
||||
if (processId <= 0) return false;
|
||||
int status;
|
||||
pid_t result = waitpid(processId, &status, WNOHANG);
|
||||
if (result == 0) return true; // Still running
|
||||
if (result == processId) {
|
||||
running = false;
|
||||
return false; // Exited
|
||||
}
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string CTorProcess::GetSocksProxy() const
|
||||
{
|
||||
return "127.0.0.1:" + std::to_string(socksPort);
|
||||
}
|
||||
|
||||
// Global convenience functions
|
||||
bool StartTorProcess(const std::string& dataDir)
|
||||
{
|
||||
return CTorProcess::GetInstance()->Start(dataDir);
|
||||
}
|
||||
|
||||
void StopTorProcess()
|
||||
{
|
||||
CTorProcess::GetInstance()->Stop();
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
// Copyright (c) 2024-2025 Triangles developers
|
||||
// Tor Process Manager - launches and manages an external Tor binary
|
||||
// Distributed under the MIT/X11 software license
|
||||
|
||||
#ifndef TRIANGLES_TOR_PROCESS_H
|
||||
#define TRIANGLES_TOR_PROCESS_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#ifdef WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
// Manages starting/stopping an external Tor process
|
||||
// Provides SOCKS5 proxy for .onion connectivity and v3 hidden service
|
||||
class CTorProcess
|
||||
{
|
||||
private:
|
||||
std::string torBinaryPath;
|
||||
std::string torDataDir;
|
||||
std::string torrcPath;
|
||||
int socksPort;
|
||||
int hiddenServicePort;
|
||||
bool running;
|
||||
|
||||
#ifdef WIN32
|
||||
HANDLE hProcess;
|
||||
DWORD processId;
|
||||
#else
|
||||
pid_t processId;
|
||||
#endif
|
||||
|
||||
// Find the Tor binary in standard locations
|
||||
std::string FindTorBinary();
|
||||
|
||||
// Generate torrc configuration file
|
||||
bool WriteTorrc();
|
||||
|
||||
// Check if SOCKS port is already in use (another Tor running)
|
||||
bool IsPortInUse(int port);
|
||||
|
||||
public:
|
||||
CTorProcess();
|
||||
~CTorProcess();
|
||||
|
||||
// Start the Tor process
|
||||
// Returns true if Tor was started or is already running
|
||||
bool Start(const std::string& dataDir, int socksPort = 19099, int hsPort = 24112);
|
||||
|
||||
// Stop the Tor process
|
||||
void Stop();
|
||||
|
||||
// Check if Tor is running
|
||||
bool IsRunning();
|
||||
|
||||
// Get the SOCKS proxy address
|
||||
std::string GetSocksProxy() const;
|
||||
|
||||
// Get the Tor binary path (for diagnostics)
|
||||
std::string GetBinaryPath() const { return torBinaryPath; }
|
||||
|
||||
// Singleton access
|
||||
static CTorProcess* GetInstance();
|
||||
};
|
||||
|
||||
// Global convenience functions
|
||||
bool StartTorProcess(const std::string& dataDir);
|
||||
void StopTorProcess();
|
||||
|
||||
#endif // TRIANGLES_TOR_PROCESS_H
|
||||
+1
-1
@@ -53,7 +53,7 @@ static const int MEMPOOL_GD_VERSION = 60002;
|
||||
|
||||
#define DISPLAY_VERSION_MAJOR 5
|
||||
#define DISPLAY_VERSION_MINOR 1
|
||||
#define DISPLAY_VERSION_REVISION 6
|
||||
#define DISPLAY_VERSION_REVISION 7
|
||||
#define DISPLAY_VERSION_BUILD 0
|
||||
|
||||
#endif
|
||||
|
||||
@@ -202,6 +202,7 @@ HEADERS += src/qt/trianglesgui.h \
|
||||
src/qt/sendcoinsdialog.h \
|
||||
src/qt/addressbookpage.h \
|
||||
src/qt/aboutdialog.h \
|
||||
src/qt/introdialog.h \
|
||||
src/qt/editaddressdialog.h \
|
||||
src/qt/trianglesaddressvalidator.h \
|
||||
src/alert.h \
|
||||
@@ -217,6 +218,7 @@ HEADERS += src/qt/trianglesgui.h \
|
||||
src/kernel.h \
|
||||
src/net_bootstrap.h \
|
||||
src/tor/onion_v3.h \
|
||||
src/tor/tor_process.h \
|
||||
src/tor/tor_crypto_compat.h \
|
||||
src/scrypt.h \
|
||||
src/pbkdf2.h \
|
||||
@@ -315,6 +317,7 @@ SOURCES += src/qt/triangles.cpp src/qt/trianglesgui.cpp \
|
||||
src/qt/coincontroltreewidget.cpp \
|
||||
src/qt/addressbookpage.cpp \
|
||||
src/qt/aboutdialog.cpp \
|
||||
src/qt/introdialog.cpp \
|
||||
src/qt/editaddressdialog.cpp \
|
||||
src/qt/trianglesaddressvalidator.cpp \
|
||||
# Old embedded Tor v2 client removed - incompatible with OpenSSL 3.x
|
||||
@@ -386,6 +389,7 @@ SOURCES += src/qt/triangles.cpp src/qt/trianglesgui.cpp \
|
||||
src/kernel.cpp \
|
||||
src/net_bootstrap.cpp \
|
||||
src/tor/onion_v3.cpp \
|
||||
src/tor/tor_process.cpp \
|
||||
src/scrypt-arm.S \
|
||||
src/scrypt-x86.S \
|
||||
src/scrypt-x86_64.S \
|
||||
|
||||
Reference in New Issue
Block a user