Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 207e1ed676 | |||
| 2fba88bfc5 | |||
| 4563e7952b | |||
| ea86ab077c | |||
| a1b137a7bb | |||
| 939606a5f7 | |||
| 73cecd90d1 | |||
| c74c92c542 | |||
| 97ae675f0a | |||
| b0b591364f | |||
| bf257858a4 | |||
| 16611efe72 | |||
| 1f3deacb7a | |||
| 8847571193 | |||
| a58eb3e9ef | |||
| dfb4b221dd | |||
| b6013edbe4 | |||
| d42c5aa799 | |||
| e8b2339339 | |||
| d242c2f37e |
@@ -0,0 +1,147 @@
|
||||
# Triangles Dynamic Seed Node - Setup Guide
|
||||
|
||||
## Overview
|
||||
|
||||
Triangles v5.5.0+ uses a dynamic HTTP seed list instead of hardcoded addresses.
|
||||
A collector script runs on a VPS alongside a Triangles node, periodically
|
||||
querying the node for known .onion peers and publishing them to a static file.
|
||||
New wallets fetch this file on startup to bootstrap peer discovery.
|
||||
|
||||
Once any wallet syncs and obtains its own .onion address, other nodes learn
|
||||
about it via P2P address exchange. The collector picks it up automatically
|
||||
on its next run. No manual intervention is needed after initial setup.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Linux VPS
|
||||
- Triangles daemon (`trianglesd`) running with Tor enabled
|
||||
- A web server (Caddy, nginx, Apache, etc.)
|
||||
- DNS control for the domain serving the seed list
|
||||
- `jq` and `curl` (`apt install jq curl`)
|
||||
|
||||
## Step 1: DNS
|
||||
|
||||
Create an A record for the seed list hostname pointing to the VPS IP address.
|
||||
|
||||
The default hostname the wallet fetches is `seeds.cryptographic-triangles.org`.
|
||||
This can be overridden per-node with the `-seedurl` flag.
|
||||
|
||||
## Step 2: Web Server
|
||||
|
||||
Create a directory for the seed file:
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /var/www/seeds
|
||||
sudo chown $USER:$USER /var/www/seeds
|
||||
```
|
||||
|
||||
Configure the web server to serve that directory on the seed list hostname.
|
||||
|
||||
**Caddy example** (add to Caddyfile):
|
||||
|
||||
```
|
||||
seeds.cryptographic-triangles.org {
|
||||
root * /var/www/seeds
|
||||
file_server
|
||||
}
|
||||
```
|
||||
|
||||
**nginx example** (add server block):
|
||||
|
||||
```
|
||||
server {
|
||||
listen 80;
|
||||
server_name seeds.cryptographic-triangles.org;
|
||||
root /var/www/seeds;
|
||||
}
|
||||
```
|
||||
|
||||
Reload the web server after making changes.
|
||||
|
||||
## Step 3: Install the Collector Script
|
||||
|
||||
```bash
|
||||
sudo cp contrib/seeds/collect-seeds.sh /usr/local/bin/collect-seeds.sh
|
||||
sudo chmod +x /usr/local/bin/collect-seeds.sh
|
||||
```
|
||||
|
||||
## Step 4: Configure and Test
|
||||
|
||||
The script communicates with `trianglesd` via JSON-RPC. It reads credentials
|
||||
from environment variables. Check `triangles.conf` for `rpcuser` and `rpcpassword`.
|
||||
|
||||
Run it manually to verify:
|
||||
|
||||
```bash
|
||||
export RPC_USER="your_rpc_username"
|
||||
export RPC_PASSWORD="your_rpc_password"
|
||||
export RPC_PORT="19112"
|
||||
export OUTPUT_FILE="/var/www/seeds/seeds.txt"
|
||||
|
||||
/usr/local/bin/collect-seeds.sh
|
||||
```
|
||||
|
||||
Expected output: `Updated /var/www/seeds/seeds.txt with N seeds`
|
||||
|
||||
The resulting file should contain one `.onion:port` entry per line:
|
||||
|
||||
```
|
||||
# Triangles seed nodes - auto-generated 2026-04-01T12:00:00Z
|
||||
exampleaddress1234567890abcdefghijklmnopqrstuvwxyz234567.onion:24112
|
||||
anotheraddress1234567890abcdefghijklmnopqrstuvwxyz23456.onion:24112
|
||||
```
|
||||
|
||||
## Step 5: Cron Job
|
||||
|
||||
Schedule the collector to run every 5 minutes:
|
||||
|
||||
```bash
|
||||
crontab -e
|
||||
```
|
||||
|
||||
Add:
|
||||
|
||||
```
|
||||
*/5 * * * * RPC_USER="your_rpc_username" RPC_PASSWORD="your_rpc_password" OUTPUT_FILE="/var/www/seeds/seeds.txt" /usr/local/bin/collect-seeds.sh >> /var/log/triangles-seeds.log 2>&1
|
||||
```
|
||||
|
||||
## Step 6: Verify End-to-End
|
||||
|
||||
From any machine:
|
||||
|
||||
```bash
|
||||
curl http://seeds.cryptographic-triangles.org/seeds.txt
|
||||
```
|
||||
|
||||
The response should list .onion addresses.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**"no onion seeds found"**
|
||||
The node has not yet learned any .onion peer addresses. Ensure Tor is enabled
|
||||
and the node has at least one connected peer. Check with `trianglesd getpeerinfo`.
|
||||
|
||||
**"RPC call failed"**
|
||||
Verify `trianglesd` is running and RPC credentials are correct:
|
||||
```bash
|
||||
curl -s --user "user:pass" --data-binary \
|
||||
'{"jsonrpc":"1.0","method":"getinfo","params":[]}' \
|
||||
http://127.0.0.1:19112/
|
||||
```
|
||||
|
||||
**seeds.txt not updating**
|
||||
Check the cron log: `tail /var/log/triangles-seeds.log`
|
||||
|
||||
## How It Works
|
||||
|
||||
1. The collector calls the `getseedlist` RPC, which returns all known .onion
|
||||
addresses from the node's address manager
|
||||
2. Results are written to a static text file served by the web server
|
||||
3. On startup, Triangles wallets fetch this file and add the addresses to
|
||||
their peer database
|
||||
4. As wallets connect and exchange addresses via P2P, new .onion addresses
|
||||
propagate across the network
|
||||
5. The collector discovers newly-propagated addresses on its next run
|
||||
|
||||
This creates a fully automatic cycle where every online wallet with a Tor
|
||||
hidden service becomes a discoverable seed node.
|
||||
@@ -0,0 +1,49 @@
|
||||
#!/bin/bash
|
||||
# Triangles Dynamic Seed Collector
|
||||
# Run via cron on a VPS that runs a Triangles node.
|
||||
# Queries the local node's getseedlist RPC for known .onion peers
|
||||
# and writes them to a static file served by a web server.
|
||||
#
|
||||
# Example cron (every 5 minutes):
|
||||
# */5 * * * * /path/to/collect-seeds.sh
|
||||
#
|
||||
# The web server (Caddy, nginx, etc.) serves the output file at:
|
||||
# http://seeds.cryptographic-triangles.org/seeds.txt
|
||||
|
||||
# Configuration
|
||||
RPC_USER="${RPC_USER:-trianglesrpc}"
|
||||
RPC_PASSWORD="${RPC_PASSWORD:-}"
|
||||
RPC_PORT="${RPC_PORT:-19112}"
|
||||
OUTPUT_FILE="${OUTPUT_FILE:-/var/www/seeds/seeds.txt}"
|
||||
|
||||
if [ -z "$RPC_PASSWORD" ]; then
|
||||
echo "Error: RPC_PASSWORD not set" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Query the node for known onion seeds
|
||||
RESPONSE=$(curl -s --user "${RPC_USER}:${RPC_PASSWORD}" \
|
||||
--data-binary '{"jsonrpc":"1.0","id":"seedcollect","method":"getseedlist","params":[]}' \
|
||||
-H 'content-type: text/plain;' \
|
||||
"http://127.0.0.1:${RPC_PORT}/" 2>/dev/null)
|
||||
|
||||
if [ $? -ne 0 ] || [ -z "$RESPONSE" ]; then
|
||||
echo "Error: RPC call failed" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract addresses and write to temp file, then atomically move
|
||||
TMPFILE=$(mktemp)
|
||||
|
||||
echo "# Triangles seed nodes - auto-generated $(date -u +%Y-%m-%dT%H:%M:%SZ)" > "$TMPFILE"
|
||||
echo "$RESPONSE" | jq -r '.result[] | .address + ":" + (.port|tostring)' >> "$TMPFILE" 2>/dev/null
|
||||
|
||||
SEED_COUNT=$(grep -c '.onion' "$TMPFILE" 2>/dev/null || echo 0)
|
||||
|
||||
if [ "$SEED_COUNT" -gt 0 ]; then
|
||||
mv "$TMPFILE" "$OUTPUT_FILE"
|
||||
echo "Updated ${OUTPUT_FILE} with ${SEED_COUNT} seeds"
|
||||
else
|
||||
rm -f "$TMPFILE"
|
||||
echo "Warning: no onion seeds found, keeping previous file" >&2
|
||||
fi
|
||||
Regular → Executable
+7
-2
@@ -15,8 +15,13 @@ if [ -e "$(which git)" ]; then
|
||||
# clean 'dirty' status of touched files that haven't been modified
|
||||
git diff >/dev/null 2>/dev/null
|
||||
|
||||
# get a string like "v0.6.0-66-g59887e8-dirty"
|
||||
DESC="$(git describe --dirty 2>/dev/null)"
|
||||
# Try exact tag match first (when building from a release tag)
|
||||
DESC="$(git describe --tags --exact-match 2>/dev/null)"
|
||||
|
||||
# If no exact match, fall back to git describe with commit distance
|
||||
if [ -z "$DESC" ]; then
|
||||
DESC="$(git describe --tags --dirty 2>/dev/null)"
|
||||
fi
|
||||
|
||||
# get a string like "2012-04-10 16:27:19 +0200"
|
||||
TIME="$(git log -n 1 --format="%ci")"
|
||||
|
||||
+2
-2
@@ -7,8 +7,8 @@
|
||||
|
||||
// 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 3
|
||||
#define CLIENT_VERSION_REVISION 9
|
||||
#define CLIENT_VERSION_MINOR 5
|
||||
#define CLIENT_VERSION_REVISION 1
|
||||
#define CLIENT_VERSION_BUILD 0
|
||||
|
||||
// Converts the parameter X to a string after macro replacement on X has been performed.
|
||||
|
||||
+67
-16
@@ -176,6 +176,12 @@ void Shutdown(void* parg)
|
||||
}
|
||||
|
||||
SecureMsgShutdown();
|
||||
|
||||
// Stop network threads FIRST so nothing references Tor objects
|
||||
nTransactionsUpdated++;
|
||||
StopNode();
|
||||
|
||||
// NOW safe to destroy Tor state - all threads have stopped
|
||||
ShutdownTorV3();
|
||||
StopEmbeddedTor();
|
||||
|
||||
@@ -194,10 +200,8 @@ void Shutdown(void* parg)
|
||||
pNotificationQueue = NULL;
|
||||
}
|
||||
|
||||
nTransactionsUpdated++;
|
||||
// CTxDB().Close();
|
||||
bitdb.Flush(false);
|
||||
StopNode();
|
||||
bitdb.Flush(true);
|
||||
fs::remove(GetPidFile());
|
||||
UnregisterWallet(pwalletMain);
|
||||
@@ -355,6 +359,7 @@ std::string HelpMessage()
|
||||
" -tor=<ip:port> " + _("Use proxy to reach tor hidden services (default: same as -proxy)") + "\n"
|
||||
" -notor " + _("Disable Tor startup and .onion connectivity") + "\n" +
|
||||
" -torsocks=<port> " + _("Set embedded or managed Tor SOCKS proxy port (default: 19099)") + "\n" +
|
||||
" -torhiddenservice " + _("Enable the managed Tor hidden service (default: 1)") + "\n" +
|
||||
" -torhsport=<port> " + _("Set embedded or managed Tor hidden service port (default: wallet listen port)") + "\n" +
|
||||
//" -dns " + _("Allow DNS lookups for -addnode, -seednode and -connect") + "\n" +
|
||||
" -port=<port> " + _("Listen for connections on <port> (default: 24112 or testnet: 24111)") + "\n" +
|
||||
@@ -373,6 +378,8 @@ std::string HelpMessage()
|
||||
" -synctime " + _("Sync time with other nodes. Disable if time on your system is precise e.g. syncing with NTP (default: 1)") + "\n" +
|
||||
" -cppolicy " + _("Sync checkpoints policy (default: strict)") + "\n" +
|
||||
" -onionseed " + _("Find peers using .onion seeds (default: 1 unless -connect)") + "\n" +
|
||||
" -seedurl=<host> " + _("HTTP seed list host (default: seeds.cryptographic-triangles.org)") + "\n" +
|
||||
" -noseedurl " + _("Disable HTTP seed list fetch on startup") + "\n" +
|
||||
" -banscore=<n> " + _("Threshold for disconnecting misbehaving peers (default: 100)") + "\n" +
|
||||
" -bantime=<n> " + _("Number of seconds to keep misbehaving peers from reconnecting (default: 86400)") + "\n" +
|
||||
" -maxreceivebuffer=<n> " + _("Maximum per-connection receive buffer, <n>*1000 bytes (default: 5000)") + "\n" +
|
||||
@@ -415,6 +422,7 @@ std::string HelpMessage()
|
||||
" -upgradewallet " + _("Upgrade wallet to latest format") + "\n" +
|
||||
" -keypool=<n> " + _("Set key pool size to <n> (default: 100)") + "\n" +
|
||||
" -rescan " + _("Rescan the block chain for missing wallet transactions") + "\n" +
|
||||
" -postibdrescan " + _("Run the wallet rescan after initial sync in a background thread (default: 1)") + "\n" +
|
||||
" -salvagewallet " + _("Attempt to recover private keys from a corrupt wallet.dat") + "\n" +
|
||||
" -checkblocks=<n> " + _("How many blocks to check at startup (default: 2500, 0 = all)") + "\n" +
|
||||
" -checklevel=<n> " + _("How thorough the block verification is (0-6, default: 1)") + "\n" +
|
||||
@@ -624,6 +632,10 @@ bool AppInit2()
|
||||
fConfChange = GetBoolArg("-confchange", false);
|
||||
fEnforceCanonical = GetBoolArg("-enforcecanonical", true);
|
||||
|
||||
fAddressIndex = GetBoolArg("-addressindex", false);
|
||||
if (fAddressIndex)
|
||||
printf("Address index enabled\n");
|
||||
|
||||
if (mapArgs.count("-mininput"))
|
||||
{
|
||||
if (!ParseMoney(mapArgs["-mininput"], nMinimumInputValue))
|
||||
@@ -752,7 +764,7 @@ bool AppInit2()
|
||||
|
||||
// Tor proxy: always configured for .onion connectivity
|
||||
CService addrOnion;
|
||||
unsigned short const onion_port = 19099;
|
||||
unsigned short const onion_port = static_cast<unsigned short>(GetArg("-torsocks", 19099));
|
||||
|
||||
if (mapArgs.count("-tor") && mapArgs["-tor"] != "0") {
|
||||
addrOnion = CService(mapArgs["-tor"], onion_port);
|
||||
@@ -1040,7 +1052,7 @@ bool AppInit2()
|
||||
printf("Rescanning last %i blocks (from block %i)...\n", pindexBest->nHeight - pindexRescan->nHeight, pindexRescan->nHeight);
|
||||
nStart = GetTimeMillis();
|
||||
bool fScannedWithIndex = false;
|
||||
if (GetBoolArg("-addressindex", false) && !GetBoolArg("-rescan"))
|
||||
if (fAddressIndex && !GetBoolArg("-rescan"))
|
||||
{
|
||||
CTxDB txdb("r");
|
||||
int nAddressIndexStartHeight = 0;
|
||||
@@ -1078,6 +1090,37 @@ bool AppInit2()
|
||||
uiInterface.InitMessage(_("Starting Tor..."));
|
||||
printf("Starting Tor process...\n");
|
||||
|
||||
// Restore hidden service secret key from wallet backup if the key
|
||||
// file is missing on disk. This preserves the .onion identity even
|
||||
// if the tor_data directory was deleted.
|
||||
if (pwalletMain && !GetBoolArg("-notor", false)) {
|
||||
std::string restoreDataPath = GetArg("-tordatadir", (GetDataDir() / "tor_data").string());
|
||||
fs::path secretKeyPath = fs::path(restoreDataPath) / "hidden_service" / "hs_ed25519_secret_key";
|
||||
|
||||
if (!fs::exists(secretKeyPath)) {
|
||||
CWalletDB walletdb(pwalletMain->strWalletFile);
|
||||
std::vector<unsigned char> backedUpKey;
|
||||
|
||||
if (walletdb.ReadSetting("tor_v3_hs_secret_key_backup", backedUpKey) &&
|
||||
backedUpKey.size() == 96) {
|
||||
fs::create_directories(secretKeyPath.parent_path());
|
||||
|
||||
std::ofstream keyFile(secretKeyPath.string().c_str(), std::ios::binary);
|
||||
if (keyFile.is_open()) {
|
||||
keyFile.write(reinterpret_cast<const char*>(backedUpKey.data()),
|
||||
backedUpKey.size());
|
||||
keyFile.close();
|
||||
printf("Restored Tor hidden service secret key from wallet backup\n");
|
||||
} else {
|
||||
printf("WARNING: Failed to write restored hs_ed25519_secret_key to %s\n",
|
||||
secretKeyPath.string().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
OPENSSL_cleanse(backedUpKey.data(), backedUpKey.size());
|
||||
}
|
||||
}
|
||||
|
||||
int64_t nTorStart = GetTimeMillis();
|
||||
bool torStarted = StartEmbeddedTor();
|
||||
StartupPerfLog("tor_start", GetTimeMillis() - nTorStart, strprintf("started=%d", torStarted));
|
||||
@@ -1100,13 +1143,14 @@ bool AppInit2()
|
||||
int64_t nTorIdentityStart = GetTimeMillis();
|
||||
LoadTorV3Config();
|
||||
TorV3Config& torConfig = GetTorV3Config();
|
||||
torConfig.enableTor = true;
|
||||
torConfig.enableHiddenService = true;
|
||||
torConfig.hiddenServicePort = GetListenPort();
|
||||
torConfig.enableTor = torStarted;
|
||||
torConfig.enableHiddenService = torStarted && CTorEmbedded::GetInstance()->IsHiddenServiceEnabled();
|
||||
torConfig.hiddenServicePort = CTorEmbedded::GetInstance()->GetHiddenServicePort();
|
||||
torConfig.torDataDirectory = torDataPath;
|
||||
std::string onionAddr;
|
||||
|
||||
if (InitTorV3()) {
|
||||
string onionAddr = CTorV3Manager::GetInstance()->GetWalletOnionAddress();
|
||||
if (torConfig.enableTor && torConfig.enableHiddenService && InitTorV3()) {
|
||||
onionAddr = CTorV3Manager::GetInstance()->GetWalletOnionAddress();
|
||||
if (!onionAddr.empty()) {
|
||||
// Write onion/hostname for compatibility with existing code paths
|
||||
fs::path onionDir = GetDataDir() / "onion";
|
||||
@@ -1118,11 +1162,15 @@ bool AppInit2()
|
||||
}
|
||||
|
||||
// Register onion address as local address for peer discovery
|
||||
AddLocal(CService(onionAddr, GetListenPort(), fNameLookup), LOCAL_MANUAL);
|
||||
AddLocal(CService(onionAddr, torConfig.hiddenServicePort, fNameLookup), LOCAL_MANUAL);
|
||||
printf("Tor V3 identity: %s\n", onionAddr.c_str());
|
||||
} else {
|
||||
printf("WARNING: Tor V3 initialized but no onion address available\n");
|
||||
}
|
||||
} else if (torStarted && !torConfig.enableHiddenService) {
|
||||
printf("Tor hidden service disabled by configuration\n");
|
||||
} else if (!torStarted) {
|
||||
printf("Skipping Tor V3 identity because the Tor backend is unavailable\n");
|
||||
} else {
|
||||
printf("WARNING: Failed to initialize Tor V3 identity\n");
|
||||
}
|
||||
@@ -1139,13 +1187,21 @@ bool AppInit2()
|
||||
while (!torOnion.empty() && (torOnion.back() == '\n' || torOnion.back() == '\r' || torOnion.back() == ' '))
|
||||
torOnion.pop_back();
|
||||
if (!torOnion.empty()) {
|
||||
AddLocal(CService(torOnion, GetListenPort(), fNameLookup), LOCAL_MANUAL);
|
||||
if (torOnion != onionAddr) {
|
||||
AddLocal(CService(torOnion, torConfig.hiddenServicePort, fNameLookup), LOCAL_MANUAL);
|
||||
}
|
||||
printf("Tor hidden service (from Tor process): %s\n", torOnion.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
StartupPerfLog("tor_setup_total", GetTimeMillis() - nTorStart);
|
||||
|
||||
// Launch background thread for Tor health monitoring and seeder maintenance
|
||||
if (torStarted) {
|
||||
if (!NewThread(ThreadTorMaintenance, NULL))
|
||||
printf("Warning: ThreadTorMaintenance could not be started\n");
|
||||
}
|
||||
}
|
||||
|
||||
// ********************************************************* Step 9: import blocks
|
||||
@@ -1249,11 +1305,6 @@ bool AppInit2()
|
||||
}
|
||||
#endif
|
||||
|
||||
// ********************************************************* Step 11.6: Address index
|
||||
fAddressIndex = GetBoolArg("-addressindex", false);
|
||||
if (fAddressIndex)
|
||||
printf("Address index enabled\n");
|
||||
|
||||
// ********************************************************* Step 11.7: SSE notification queue
|
||||
if (GetBoolArg("-ssenotify", false))
|
||||
{
|
||||
|
||||
+77
-38
@@ -110,11 +110,54 @@ struct CHeaderSyncNode
|
||||
|
||||
static std::map<uint256, CHeaderSyncNode> mapHeaderSync;
|
||||
static uint256 hashBestHeaderSync = 0;
|
||||
static CCriticalSection cs_PostIbdWork;
|
||||
static bool fPostIbdWorkStarted = false;
|
||||
|
||||
static const unsigned int MAX_HEADER_SYNC_CACHE = 50000;
|
||||
static const unsigned int HEADER_DOWNLOAD_WINDOW = 128;
|
||||
static const int64_t HEADER_REQUEST_TIMEOUT_MICROS = 30 * 1000000;
|
||||
|
||||
static void ThreadPostIbdWork(void* parg)
|
||||
{
|
||||
RenameThread("Triangles-postibd");
|
||||
|
||||
try
|
||||
{
|
||||
if (!fShutdown && pwalletMain && GetBoolArg("-postibdrescan", true))
|
||||
{
|
||||
printf("Starting post-IBD wallet rescan from genesis in background...\n");
|
||||
uiInterface.InitMessage(_("Rescanning wallet in background..."));
|
||||
int nFound = 0;
|
||||
bool fUsedIndex = false;
|
||||
if (fAddressIndex)
|
||||
{
|
||||
fUsedIndex = pwalletMain->ScanForWalletTransactionsFromIndex(pindexGenesisBlock, true, &nFound);
|
||||
if (!fUsedIndex)
|
||||
printf("Indexed wallet rescan failed, falling back to full rescan.\n");
|
||||
}
|
||||
if (!fUsedIndex)
|
||||
nFound = pwalletMain->ScanForWalletTransactions(pindexGenesisBlock, true);
|
||||
printf("Post-IBD wallet rescan complete: %d transactions found (indexed=%d)\n", nFound, fUsedIndex);
|
||||
}
|
||||
|
||||
if (!fShutdown && fSecMsgEnabled)
|
||||
{
|
||||
printf("Starting post-IBD secure message chain scan in background...\n");
|
||||
uiInterface.InitMessage(_("Scanning for secure messages in background..."));
|
||||
SecureMsgScanBlockChain();
|
||||
printf("Post-IBD secure message chain scan complete\n");
|
||||
}
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
PrintExceptionContinue(&e, "ThreadPostIbdWork()");
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
PrintExceptionContinue(NULL, "ThreadPostIbdWork()");
|
||||
}
|
||||
}
|
||||
|
||||
static uint256 GetHeaderSyncTrust(unsigned int nBits)
|
||||
{
|
||||
CBigNum bnTarget;
|
||||
@@ -2043,8 +2086,8 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck)
|
||||
return error("ConnectBlock() : UpdateTxIndex failed");
|
||||
}
|
||||
|
||||
// Update address index (skip during IBD - will be rebuilt on next start with -reindex)
|
||||
if (fAddressIndex && !fIsInitialDownload)
|
||||
// Update address index
|
||||
if (fAddressIndex)
|
||||
{
|
||||
for (unsigned int i = 0; i < vtx.size(); i++)
|
||||
{
|
||||
@@ -2406,23 +2449,23 @@ bool CBlock::SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew)
|
||||
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)
|
||||
// Run expensive post-IBD scans in the background so reaching tip
|
||||
// is not blocked by wallet/message index rebuild work.
|
||||
bool fStartPostIbdWork = false;
|
||||
{
|
||||
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);
|
||||
LOCK(cs_PostIbdWork);
|
||||
if (!fPostIbdWorkStarted)
|
||||
{
|
||||
fPostIbdWorkStarted = true;
|
||||
fStartPostIbdWork = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Secure messaging: scan chain for public keys needed to decrypt messages
|
||||
if (fSecMsgEnabled)
|
||||
if (fStartPostIbdWork && !NewThread(ThreadPostIbdWork, NULL))
|
||||
{
|
||||
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");
|
||||
LOCK(cs_PostIbdWork);
|
||||
fPostIbdWorkStarted = false;
|
||||
printf("Warning: post-IBD background work thread could not be started; scans skipped.\n");
|
||||
}
|
||||
}
|
||||
fWasInitialDownload = fIsInitialDownload;
|
||||
@@ -2568,7 +2611,17 @@ bool CBlock::AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos, const u
|
||||
|
||||
// New best — keep the batch open so SetBestChain can add ConnectBlock
|
||||
// writes to the same transaction, cutting the per-block commit count in half.
|
||||
// v5.4: deterministic tiebreaker — when two chains have equal trust,
|
||||
// all nodes agree on the one whose tip has the lower block hash.
|
||||
// This prevents permanent forks from PoS blocks with identical difficulty.
|
||||
bool fNewBest = false;
|
||||
if (pindexNew->nChainTrust > nBestChainTrust)
|
||||
fNewBest = true;
|
||||
else if (pindexNew->nChainTrust == nBestChainTrust && pindexBest &&
|
||||
pindexNew->GetBlockHash() < pindexBest->GetBlockHash())
|
||||
fNewBest = true;
|
||||
|
||||
if (fNewBest)
|
||||
{
|
||||
if (!SetBestChain(txdb, pindexNew))
|
||||
return false;
|
||||
@@ -2719,7 +2772,7 @@ bool CBlock::AcceptBlock()
|
||||
if (nHeight % 10000 == 0 || nHeight > 2186900)
|
||||
printf("ProcessBlock(): Check proof-of-stake/work OK for block %d\n", nHeight);
|
||||
// Check timestamp against prev
|
||||
if (GetBlockTime() <= pindexPrev->GetPastTimeLimit() || FutureDrift(GetBlockTime()) < pindexPrev->GetBlockTime())
|
||||
if (GetBlockTime() <= pindexPrev->GetPastTimeLimit() || FutureDrift(GetBlockTime(), nHeight) < pindexPrev->GetBlockTime())
|
||||
return error("AcceptBlock() : block's timestamp is too early");
|
||||
|
||||
// Check that all transactions are finalized
|
||||
@@ -2778,9 +2831,6 @@ bool CBlock::AcceptBlock()
|
||||
pnode->PushInventory(CInv(MSG_BLOCK, hash));
|
||||
}
|
||||
|
||||
// triangles: check pending sync-checkpoint
|
||||
Checkpoints::AcceptPendingSyncCheckpoint();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2819,7 +2869,7 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock)
|
||||
// triangles: check proof-of-stake
|
||||
// Limited duplicity on stake: prevents block flood attack
|
||||
// Duplicate stake allowed only when there is orphan child block
|
||||
if (pblock->IsProofOfStake() && setStakeSeen.count(pblock->GetProofOfStake()) && !mapOrphanBlocksByPrev.count(hash) && !Checkpoints::WantedByPendingSyncCheckpoint(hash))
|
||||
if (pblock->IsProofOfStake() && setStakeSeen.count(pblock->GetProofOfStake()) && !mapOrphanBlocksByPrev.count(hash))
|
||||
return error("ProcessBlock() : duplicate proof-of-stake (%s, %d) for block %s", pblock->GetProofOfStake().first.ToString().c_str(), pblock->GetProofOfStake().second, hash.ToString().c_str());
|
||||
|
||||
// Preliminary checks
|
||||
@@ -2833,12 +2883,12 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock)
|
||||
}
|
||||
|
||||
// Anti-spam: reject blocks with insufficient difficulty to prevent memory flooding.
|
||||
// Use sync checkpoint as reference; fall back to chain tip if checkpoint is genesis.
|
||||
CBlockIndex* pcheckpoint = Checkpoints::GetLastSyncCheckpoint();
|
||||
if (!pcheckpoint || pcheckpoint->nHeight == 0)
|
||||
// Use the most recent hardened checkpoint we know about; fall back to the chain tip.
|
||||
CBlockIndex* pcheckpoint = Checkpoints::GetLastCheckpoint(mapBlockIndex);
|
||||
if (!pcheckpoint)
|
||||
pcheckpoint = pindexBest;
|
||||
|
||||
if (pcheckpoint && pblock->hashPrevBlock != hashBestChain && !Checkpoints::WantedByPendingSyncCheckpoint(hash))
|
||||
if (pcheckpoint && pblock->hashPrevBlock != hashBestChain)
|
||||
{
|
||||
int64_t deltaTime = pblock->GetBlockTime() - pcheckpoint->nTime;
|
||||
CBigNum bnNewBlock;
|
||||
@@ -2867,10 +2917,6 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock)
|
||||
}
|
||||
}
|
||||
|
||||
// triangles: ask for pending sync-checkpoint if any
|
||||
if (!IsInitialBlockDownload())
|
||||
Checkpoints::AskForPendingSyncCheckpoint(pfrom);
|
||||
|
||||
// If don't already have its previous block, shunt it off to holding area until we get it
|
||||
if (!mapBlockIndex.count(pblock->hashPrevBlock))
|
||||
{
|
||||
@@ -2881,7 +2927,7 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock)
|
||||
{
|
||||
// Limited duplicity on stake: prevents block flood attack
|
||||
// Duplicate stake allowed only when there is orphan child block
|
||||
if (setStakeSeenOrphan.count(pblock2->GetProofOfStake()) && !mapOrphanBlocksByPrev.count(hash) && !Checkpoints::WantedByPendingSyncCheckpoint(hash))
|
||||
if (setStakeSeenOrphan.count(pblock2->GetProofOfStake()) && !mapOrphanBlocksByPrev.count(hash))
|
||||
return error("ProcessBlock() : duplicate proof-of-stake (%s, %d) for orphan block %s", pblock2->GetProofOfStake().first.ToString().c_str(), pblock2->GetProofOfStake().second, hash.ToString().c_str());
|
||||
else
|
||||
setStakeSeenOrphan.insert(pblock2->GetProofOfStake());
|
||||
@@ -2969,10 +3015,6 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock)
|
||||
nQueued, hash.ToString().substr(0,20).c_str());
|
||||
}
|
||||
|
||||
// triangles: if responsible for sync-checkpoint send it
|
||||
if (pfrom && !CSyncCheckpoint::strMasterPrivKey.empty())
|
||||
Checkpoints::SendSyncCheckpoint(Checkpoints::AutoSelectSyncCheckpoint());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2999,13 +3041,13 @@ bool CBlock::SignBlock(CWallet& wallet, int64_t nFees)
|
||||
{
|
||||
if (wallet.CreateCoinStake(wallet, nBits, nSearchTime-nLastCoinStakeSearchTime, nFees, txCoinStake, key))
|
||||
{
|
||||
if (txCoinStake.nTime >= max(pindexBest->GetPastTimeLimit()+1, PastDrift(pindexBest->GetBlockTime())))
|
||||
if (txCoinStake.nTime >= max(pindexBest->GetPastTimeLimit()+1, PastDrift(pindexBest->GetBlockTime(), pindexBest->nHeight + 1)))
|
||||
{
|
||||
// make sure coinstake would meet timestamp protocol
|
||||
// as it would be the same as the block timestamp
|
||||
vtx[0].nTime = nTime = txCoinStake.nTime;
|
||||
nTime = max(pindexBest->GetPastTimeLimit()+1, GetMaxTransactionTime());
|
||||
nTime = max(GetBlockTime(), PastDrift(pindexBest->GetBlockTime()));
|
||||
nTime = max(GetBlockTime(), PastDrift(pindexBest->GetBlockTime(), pindexBest->nHeight + 1));
|
||||
|
||||
// we have to make sure that we have no future timestamps in
|
||||
// our transactions set
|
||||
@@ -3849,9 +3891,6 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
|
||||
|
||||
cPeerBlockCounts.input(pfrom->nStartingHeight);
|
||||
|
||||
// triangles: ask for pending sync-checkpoint if any
|
||||
if (!IsInitialBlockDownload())
|
||||
Checkpoints::AskForPendingSyncCheckpoint(pfrom);
|
||||
}
|
||||
|
||||
|
||||
|
||||
+7
-3
@@ -29,6 +29,7 @@ class CNode;
|
||||
static const int CUTOFF_POW_BLOCK = 9000;
|
||||
static const int CRAPCHAIN_CUTOFF_BLOCK = 17691; // pre-Pharao (version 4) blockchain until block 17691
|
||||
static const int FORK_HEIGHT_V5 = 17651; // v5 hard fork: decentralization + Tor v3 (next block after last checkpoint)
|
||||
static const int FORK_HEIGHT_V5_4 = 2186941; // v5.4: tighter timestamps, deterministic fork resolution
|
||||
|
||||
static const unsigned int MAX_BLOCK_SIZE = 1000000;
|
||||
static const unsigned int MAX_BLOCK_SIZE_GEN = MAX_BLOCK_SIZE/2;
|
||||
@@ -39,7 +40,7 @@ static const unsigned int MAX_ORPHAN_BLOCKS_IBD = 4000;
|
||||
static const unsigned int MAX_INV_SZ = 50000;
|
||||
static const int64_t MIN_TX_FEE = (1 * CENT) / 100;
|
||||
static const int64_t MIN_RELAY_TX_FEE = (1 * CENT) / 100;
|
||||
static const int64_t MAX_MONEY = 222222 * COIN;
|
||||
static const int64_t MAX_MONEY = 2222222 * COIN;
|
||||
static const int64_t COIN_YEAR_REWARD = 33 * CENT; // 33% per year
|
||||
static const int64_t MAX_TRI_PROOF_OF_STAKE = 0.33 * COIN;
|
||||
static const int MODIFIER_INTERVAL_SWITCH = 1;
|
||||
@@ -56,8 +57,11 @@ static const int fHaveUPnP = false;
|
||||
|
||||
static const uint256 hashGenesisBlockOfficial("0x7e7a6e4dd5fe895106fca912dfbacaeaf2a89e76c6a588df8ff96e0e18b96021");
|
||||
static const uint256 hashGenesisBlockTestNet ("0x7e7a6e4dd5fe895106fca912dfbacaeaf2a89e76c6a588df8ff96e0e18b96021");
|
||||
inline int64_t PastDrift(int64_t nTime) { return nTime - 10 * 60; } // up to 10 minutes from the past
|
||||
inline int64_t FutureDrift(int64_t nTime) { return nTime + 10 * 60; } // up to 10 minutes from the future
|
||||
inline int64_t GetMaxTimeDrift(int nHeight) { return (nHeight >= FORK_HEIGHT_V5_4) ? 3 * 60 : 10 * 60; }
|
||||
inline int64_t PastDrift(int64_t nTime, int nHeight) { return nTime - GetMaxTimeDrift(nHeight); }
|
||||
inline int64_t FutureDrift(int64_t nTime, int nHeight) { return nTime + GetMaxTimeDrift(nHeight); }
|
||||
inline int64_t PastDrift(int64_t nTime) { return PastDrift(nTime, nBestHeight); }
|
||||
inline int64_t FutureDrift(int64_t nTime) { return FutureDrift(nTime, nBestHeight); }
|
||||
|
||||
|
||||
extern CScript COINBASE_FLAGS;
|
||||
|
||||
+1
-1
@@ -365,7 +365,7 @@ CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake, int64_t* pFees)
|
||||
// Fill in header
|
||||
pblock->hashPrevBlock = pindexPrev->GetBlockHash();
|
||||
pblock->nTime = max(pindexPrev->GetPastTimeLimit()+1, pblock->GetMaxTransactionTime());
|
||||
pblock->nTime = max(pblock->GetBlockTime(), PastDrift(pindexPrev->GetBlockTime()));
|
||||
pblock->nTime = max(pblock->GetBlockTime(), PastDrift(pindexPrev->GetBlockTime(), pindexPrev->nHeight + 1));
|
||||
if (!fProofOfStake)
|
||||
pblock->UpdateTime(pindexPrev);
|
||||
pblock->nNonce = 0;
|
||||
|
||||
+188
-72
@@ -13,6 +13,9 @@
|
||||
#include "ui_interface.h"
|
||||
#include "onionseed.h"
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <sstream>
|
||||
|
||||
#ifdef WIN32
|
||||
#include <string.h>
|
||||
#endif
|
||||
@@ -41,8 +44,8 @@ void ThreadOpenAddedConnections2(void* parg);
|
||||
#ifdef USE_UPNP
|
||||
void ThreadMapPort2(void* parg);
|
||||
#endif
|
||||
void ThreadDNSAddressSeed(void* parg);
|
||||
void ThreadDNSAddressSeed2(void* parg);
|
||||
void ThreadHTTPSeedFetch(void* parg);
|
||||
void ThreadHTTPSeedFetch2(void* parg);
|
||||
bool OpenNetworkConnection(const CAddress& addrConnect, CSemaphoreGrant *grantOutbound = NULL, const char *strDest = NULL, bool fOneShot = false);
|
||||
|
||||
|
||||
@@ -878,10 +881,19 @@ void ThreadSocketHandler2(void* parg)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (vNodes.size() != nPrevNodeCount)
|
||||
{
|
||||
nPrevNodeCount = vNodes.size();
|
||||
uiInterface.NotifyNumConnectionsChanged(vNodes.size());
|
||||
// Read vNodes.size() under the lock to avoid data race
|
||||
unsigned int nNodeCount;
|
||||
{
|
||||
LOCK(cs_vNodes);
|
||||
nNodeCount = vNodes.size();
|
||||
}
|
||||
if (nNodeCount != nPrevNodeCount)
|
||||
{
|
||||
nPrevNodeCount = nNodeCount;
|
||||
if (!fShutdown)
|
||||
uiInterface.NotifyNumConnectionsChanged(nNodeCount);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1013,7 +1025,7 @@ void ThreadSocketHandler2(void* parg)
|
||||
for (CNode* pnode : vNodesCopy)
|
||||
{
|
||||
if (fShutdown)
|
||||
return;
|
||||
break;
|
||||
|
||||
//
|
||||
// Receive
|
||||
@@ -1039,7 +1051,7 @@ void ThreadSocketHandler2(void* parg)
|
||||
if (!pnode->ReceiveMsgBytes(pchBuf, nBytes))
|
||||
pnode->CloseSocketDisconnect();
|
||||
pnode->nLastRecv = GetTime();
|
||||
pnode->nRecvBytes += nBytes;
|
||||
pnode->nRecvBytes += nBytes;
|
||||
}
|
||||
else if (nBytes == 0)
|
||||
{
|
||||
@@ -1104,6 +1116,8 @@ void ThreadSocketHandler2(void* parg)
|
||||
pnode->Release();
|
||||
}
|
||||
|
||||
if (fShutdown)
|
||||
return;
|
||||
MilliSleep(10);
|
||||
}
|
||||
}
|
||||
@@ -1375,9 +1389,9 @@ void ThreadOnionSeed(void* parg)
|
||||
|
||||
|
||||
|
||||
// Hardcoded seeds removed - peer discovery is now fully dynamic via HTTP seed list.
|
||||
// See: seeds.cryptographic-triangles.org
|
||||
unsigned int pnSeed[] = {
|
||||
0xCE58E9C2, // DNS2-OpenClaw: 194.233.88.206
|
||||
0x13A7D04A, // DNS3-Sami: 74.208.167.19
|
||||
};
|
||||
|
||||
void DumpAddresses()
|
||||
@@ -1419,56 +1433,166 @@ void ThreadDumpAddress(void* parg)
|
||||
printf("ThreadDumpAddress exited\n");
|
||||
}
|
||||
|
||||
void ThreadDNSAddressSeed2(void* parg)
|
||||
void ThreadHTTPSeedFetch2(void* parg)
|
||||
{
|
||||
static const char* strDNSSeed[] = {
|
||||
"seed1.cryptographic-triangles.org",
|
||||
"seed2.cryptographic-triangles.org",
|
||||
"seed3.cryptographic-triangles.org",
|
||||
"backup-seed.cryptographic-triangles.org",
|
||||
};
|
||||
static const char* DEFAULT_SEED_URL_HOST = "seeds.cryptographic-triangles.org";
|
||||
static const char* DEFAULT_SEED_URL_PATH = "/seeds.txt";
|
||||
static const int HTTP_PORT = 80;
|
||||
|
||||
printf("Loading addresses from DNS seeds...\n");
|
||||
int found = 0;
|
||||
std::string seedHost = GetArg("-seedurl", DEFAULT_SEED_URL_HOST);
|
||||
std::string seedPath = DEFAULT_SEED_URL_PATH;
|
||||
|
||||
for (unsigned int seed_idx = 0; seed_idx < ARRAYLEN(strDNSSeed); seed_idx++)
|
||||
{
|
||||
if (fShutdown)
|
||||
// Allow full URL override: -seedurl=myhost.com/path/seeds.txt
|
||||
size_t slashPos = seedHost.find('/');
|
||||
if (slashPos != std::string::npos) {
|
||||
seedPath = seedHost.substr(slashPos);
|
||||
seedHost = seedHost.substr(0, slashPos);
|
||||
}
|
||||
|
||||
printf("Fetching seed list from http://%s%s ...\n", seedHost.c_str(), seedPath.c_str());
|
||||
|
||||
try {
|
||||
boost::asio::io_context io_context;
|
||||
boost::asio::ip::tcp::resolver resolver(io_context);
|
||||
|
||||
boost::system::error_code resolve_ec;
|
||||
auto endpoints = resolver.resolve(seedHost, std::to_string(HTTP_PORT), resolve_ec);
|
||||
if (resolve_ec) {
|
||||
printf("HTTP seed fetch: cannot resolve %s (%s)\n", seedHost.c_str(), resolve_ec.message().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
vector<CNetAddr> vaddr;
|
||||
if (LookupHost(strDNSSeed[seed_idx], vaddr))
|
||||
boost::asio::ip::tcp::socket socket(io_context);
|
||||
boost::asio::connect(socket, endpoints);
|
||||
|
||||
std::string request =
|
||||
"GET " + seedPath + " HTTP/1.1\r\n"
|
||||
"Host: " + seedHost + "\r\n"
|
||||
"Connection: close\r\n"
|
||||
"User-Agent: Triangles\r\n"
|
||||
"\r\n";
|
||||
boost::asio::write(socket, boost::asio::buffer(request));
|
||||
|
||||
// Read response
|
||||
boost::asio::streambuf response_buf;
|
||||
boost::asio::read_until(socket, response_buf, "\r\n\r\n");
|
||||
|
||||
std::istream response_stream(&response_buf);
|
||||
std::string http_version;
|
||||
unsigned int status_code = 0;
|
||||
response_stream >> http_version >> status_code;
|
||||
std::string status_message;
|
||||
std::getline(response_stream, status_message);
|
||||
|
||||
if (status_code != 200) {
|
||||
printf("HTTP seed fetch: got status %u from %s\n", status_code, seedHost.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip remaining headers
|
||||
std::string header_line;
|
||||
while (std::getline(response_stream, header_line) && header_line != "\r") {}
|
||||
|
||||
// Read body (remainder in buffer + rest from socket)
|
||||
std::string body;
|
||||
|
||||
// First, grab anything already buffered past the headers
|
||||
if (response_buf.size() > 0) {
|
||||
std::istream body_stream(&response_buf);
|
||||
std::ostringstream oss;
|
||||
oss << body_stream.rdbuf();
|
||||
body = oss.str();
|
||||
}
|
||||
|
||||
// Read rest until EOF
|
||||
boost::system::error_code ec;
|
||||
while (boost::asio::read(socket, response_buf, boost::asio::transfer_at_least(1), ec)) {
|
||||
std::istream s(&response_buf);
|
||||
std::ostringstream oss;
|
||||
oss << s.rdbuf();
|
||||
body += oss.str();
|
||||
}
|
||||
|
||||
// Parse one address per line: "address:port" or just "address"
|
||||
int found = 0;
|
||||
std::istringstream lines(body);
|
||||
std::string line;
|
||||
while (std::getline(lines, line))
|
||||
{
|
||||
for (CNetAddr& ip : vaddr)
|
||||
{
|
||||
CAddress addr(CService(ip, GetDefaultPort()));
|
||||
if (fShutdown)
|
||||
return;
|
||||
|
||||
// Trim whitespace and carriage returns
|
||||
while (!line.empty() && (line.back() == '\r' || line.back() == ' ' || line.back() == '\t'))
|
||||
line.pop_back();
|
||||
while (!line.empty() && (line.front() == ' ' || line.front() == '\t'))
|
||||
line.erase(line.begin());
|
||||
|
||||
if (line.empty() || line[0] == '#')
|
||||
continue;
|
||||
|
||||
// Parse address:port
|
||||
std::string addrStr = line;
|
||||
int port = GetDefaultPort();
|
||||
|
||||
// For .onion addresses, the last colon before port is after ".onion"
|
||||
size_t onionPos = addrStr.find(".onion:");
|
||||
if (onionPos != std::string::npos) {
|
||||
port = atoi(addrStr.substr(onionPos + 7).c_str());
|
||||
addrStr = addrStr.substr(0, onionPos + 6); // keep ".onion"
|
||||
} else if (addrStr.find(".onion") == std::string::npos) {
|
||||
// Clearnet address - find last colon for port
|
||||
size_t colonPos = addrStr.rfind(':');
|
||||
if (colonPos != std::string::npos) {
|
||||
port = atoi(addrStr.substr(colonPos + 1).c_str());
|
||||
addrStr = addrStr.substr(0, colonPos);
|
||||
}
|
||||
}
|
||||
|
||||
if (port <= 0 || port > 65535)
|
||||
port = GetDefaultPort();
|
||||
|
||||
CNetAddr parsed;
|
||||
bool resolved = parsed.SetSpecial(addrStr);
|
||||
if (!resolved) {
|
||||
std::vector<CNetAddr> vIP;
|
||||
if (LookupHost(addrStr.c_str(), vIP, 1, false) && !vIP.empty()) {
|
||||
parsed = vIP[0];
|
||||
resolved = true;
|
||||
}
|
||||
}
|
||||
if (resolved) {
|
||||
CAddress addr(CService(parsed, port));
|
||||
addr.nTime = GetTime() - 3*24*60*60; // 3 days ago
|
||||
addrman.Add(addr, CNetAddr(strDNSSeed[seed_idx], true));
|
||||
addrman.Add(addr, CNetAddr("http-seed", true));
|
||||
found++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
printf("%d addresses found from DNS seeds\n", found);
|
||||
printf("%d addresses found from HTTP seed list (%s)\n", found, seedHost.c_str());
|
||||
|
||||
} catch (std::exception& e) {
|
||||
printf("HTTP seed fetch failed: %s\n", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void ThreadDNSAddressSeed(void* parg)
|
||||
void ThreadHTTPSeedFetch(void* parg)
|
||||
{
|
||||
RenameThread("Triangles-dnsseed");
|
||||
RenameThread("Triangles-httpseed");
|
||||
try
|
||||
{
|
||||
vnThreadsRunning[THREAD_DNSSEED]++;
|
||||
ThreadDNSAddressSeed2(parg);
|
||||
vnThreadsRunning[THREAD_DNSSEED]--;
|
||||
vnThreadsRunning[THREAD_HTTPSEED]++;
|
||||
ThreadHTTPSeedFetch2(parg);
|
||||
vnThreadsRunning[THREAD_HTTPSEED]--;
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
vnThreadsRunning[THREAD_DNSSEED]--;
|
||||
PrintException(&e, "ThreadDNSAddressSeed()");
|
||||
vnThreadsRunning[THREAD_HTTPSEED]--;
|
||||
PrintException(&e, "ThreadHTTPSeedFetch()");
|
||||
} catch (...) {
|
||||
vnThreadsRunning[THREAD_DNSSEED]--;
|
||||
PrintException(NULL, "ThreadDNSAddressSeed()");
|
||||
vnThreadsRunning[THREAD_HTTPSEED]--;
|
||||
PrintException(NULL, "ThreadHTTPSeedFetch()");
|
||||
}
|
||||
printf("ThreadDNSAddressSeed exited\n");
|
||||
printf("ThreadHTTPSeedFetch exited\n");
|
||||
}
|
||||
|
||||
void ThreadOpenConnections(void* parg)
|
||||
@@ -1575,30 +1699,8 @@ void ThreadOpenConnections2(void* parg)
|
||||
if (fShutdown)
|
||||
return;
|
||||
|
||||
// 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.
|
||||
{
|
||||
LOCK(cs_vNodes);
|
||||
bool fNoOutbound = true;
|
||||
for (CNode* pnode : vNodes) {
|
||||
if (!pnode->fInbound) { fNoOutbound = false; break; }
|
||||
}
|
||||
if (fNoOutbound && (GetTime() - nStart > 10) && !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 10s, added %d hardcoded seeds\n", (int)vAdd.size());
|
||||
}
|
||||
}
|
||||
// Hardcoded seed fallback removed - peer discovery is now fully dynamic
|
||||
// via HTTP seed list from seeds.cryptographic-triangles.org
|
||||
|
||||
//
|
||||
// Choose an address to connect to based on most recently seen
|
||||
@@ -1832,6 +1934,9 @@ void ThreadMessageHandler2(void* parg)
|
||||
pnodeTrickle = vNodesCopy[GetRand(vNodesCopy.size())];
|
||||
for (CNode* pnode : vNodesCopy)
|
||||
{
|
||||
if (fShutdown)
|
||||
break;
|
||||
|
||||
// Receive messages
|
||||
{
|
||||
TRY_LOCK(pnode->cs_vRecvMsg, lockRecv);
|
||||
@@ -1839,8 +1944,6 @@ void ThreadMessageHandler2(void* parg)
|
||||
if (!ProcessMessages(pnode))
|
||||
pnode->CloseSocketDisconnect();
|
||||
}
|
||||
if (fShutdown)
|
||||
return;
|
||||
|
||||
// Send messages
|
||||
{
|
||||
@@ -1848,8 +1951,6 @@ void ThreadMessageHandler2(void* parg)
|
||||
if (lockSend)
|
||||
SendMessages(pnode, pnode == pnodeTrickle);
|
||||
}
|
||||
if (fShutdown)
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
@@ -2102,9 +2203,11 @@ void StartNode(void* parg)
|
||||
if (fUseUPnP)
|
||||
MapPort();
|
||||
|
||||
// DNS seed lookup
|
||||
if (!NewThread(ThreadDNSAddressSeed, NULL))
|
||||
printf("Error: NewThread(ThreadDNSAddressSeed) failed\n");
|
||||
// HTTP seed list fetch (replaces DNS seeds)
|
||||
if (GetBoolArg("-noseedurl", false))
|
||||
printf("HTTP seed fetch disabled\n");
|
||||
else if (!NewThread(ThreadHTTPSeedFetch, NULL))
|
||||
printf("Error: NewThread(ThreadHTTPSeedFetch) failed\n");
|
||||
|
||||
// Send and receive from sockets, accept connections
|
||||
if (!NewThread(ThreadSocketHandler, NULL))
|
||||
@@ -2162,7 +2265,7 @@ bool StopNode()
|
||||
#ifdef USE_UPNP
|
||||
if (vnThreadsRunning[THREAD_UPNP] > 0) printf("ThreadMapPort still running\n");
|
||||
#endif
|
||||
if (vnThreadsRunning[THREAD_DNSSEED] > 0) printf("ThreadDNSAddressSeed still running\n");
|
||||
if (vnThreadsRunning[THREAD_HTTPSEED] > 0) printf("ThreadHTTPSeedFetch still running\n");
|
||||
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");
|
||||
@@ -2180,6 +2283,18 @@ bool StopNode()
|
||||
}
|
||||
MilliSleep(50);
|
||||
DumpAddresses();
|
||||
|
||||
// Force-disconnect and clean up all remaining nodes now that threads have stopped.
|
||||
// Close sockets first so any lingering I/O fails immediately.
|
||||
{
|
||||
LOCK(cs_vNodes);
|
||||
for (CNode* pnode : vNodes)
|
||||
{
|
||||
pnode->CloseSocketDisconnect();
|
||||
pnode->Cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2191,7 +2306,8 @@ public:
|
||||
}
|
||||
~CNetCleanup()
|
||||
{
|
||||
// Close sockets
|
||||
// Close sockets - acquire lock in case other threads are still winding down
|
||||
LOCK(cs_vNodes);
|
||||
for (CNode* pnode : vNodes)
|
||||
if (pnode->hSocket != INVALID_SOCKET)
|
||||
closesocket(pnode->hSocket);
|
||||
|
||||
@@ -103,7 +103,7 @@ enum threadId
|
||||
THREAD_MESSAGEHANDLER,
|
||||
THREAD_RPCLISTENER,
|
||||
THREAD_UPNP,
|
||||
THREAD_DNSSEED,
|
||||
THREAD_HTTPSEED,
|
||||
THREAD_ADDEDCONNECTIONS,
|
||||
THREAD_DUMPADDRESS,
|
||||
THREAD_RPCHANDLER,
|
||||
|
||||
+2
-6
@@ -24,13 +24,9 @@ namespace NetBootstrap {
|
||||
NULL
|
||||
};
|
||||
|
||||
// Legacy IP seed nodes for old wallet compatibility
|
||||
// These should be actual IP addresses of stable nodes
|
||||
// Hardcoded seeds removed - peer discovery is now fully dynamic via HTTP seed list.
|
||||
// See: seeds.cryptographic-triangles.org
|
||||
static const unsigned int pnSeed[] __attribute__((unused)) = {
|
||||
// Format: IP addresses in network byte order (little-endian)
|
||||
// For IP a.b.c.d: (d << 24) | (c << 16) | (b << 8) | a
|
||||
0xCE58E9C2, // DNS2-OpenClaw: 194.233.88.206
|
||||
0x13A7D04A, // DNS3-Sami: 74.208.167.19
|
||||
};
|
||||
|
||||
// Network protocol compatibility settings
|
||||
|
||||
+10
-5
@@ -6,6 +6,7 @@
|
||||
#include "netbase.h"
|
||||
#include "util.h"
|
||||
#include "sync.h"
|
||||
#include <openssl/evp.h>
|
||||
|
||||
#ifndef WIN32
|
||||
#include <sys/fcntl.h>
|
||||
@@ -860,15 +861,19 @@ std::string CNetAddr::ToStringIP() const
|
||||
unsigned char addr35[35];
|
||||
memcpy(addr35, tor_v3_pubkey, 32);
|
||||
// Compute checksum: SHA3-256(".onion checksum" || pubkey || version)[:2]
|
||||
// For now use a simplified checksum from the stored data
|
||||
unsigned char checksumInput[15 + 32 + 1];
|
||||
memcpy(checksumInput, ".onion checksum", 15);
|
||||
memcpy(checksumInput + 15, tor_v3_pubkey, 32);
|
||||
checksumInput[47] = 0x03; // version
|
||||
// SHA-256 as fallback (SHA3-256 via tor_crypto_compat.h for full impl)
|
||||
uint256 hash = Hash(checksumInput, checksumInput + 48);
|
||||
addr35[32] = ((unsigned char*)&hash)[0];
|
||||
addr35[33] = ((unsigned char*)&hash)[1];
|
||||
unsigned char sha3hash[32];
|
||||
unsigned int sha3len = 0;
|
||||
EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
|
||||
EVP_DigestInit_ex(mdctx, EVP_sha3_256(), NULL);
|
||||
EVP_DigestUpdate(mdctx, checksumInput, 48);
|
||||
EVP_DigestFinal_ex(mdctx, sha3hash, &sha3len);
|
||||
EVP_MD_CTX_free(mdctx);
|
||||
addr35[32] = sha3hash[0];
|
||||
addr35[33] = sha3hash[1];
|
||||
addr35[34] = 0x03; // version
|
||||
return EncodeBase32(addr35, 35) + ".onion";
|
||||
}
|
||||
|
||||
+3
-5
@@ -2,12 +2,10 @@
|
||||
#ifndef TRIANGLES_ONIONSEED_H
|
||||
#define TRIANGLES_ONIONSEED_H
|
||||
|
||||
// hidden service seeds
|
||||
// v5 hard fork: v2 onion seeds removed (Tor v2 deprecated Oct 2021)
|
||||
// v3 onion seeds will be added when bootstrap nodes are deployed
|
||||
// Onion seeds are now fetched dynamically via HTTP seed list.
|
||||
// No hardcoded onion addresses - they go stale when Tor services restart.
|
||||
// See: seeds.cryptographic-triangles.org
|
||||
static const char *strMainNetOnionSeed[][1] = {
|
||||
{"gxvrhv3qitnc6kobrhsrse46bmcfitnybapor3or3oczzuxn6hfzxyid.onion"},
|
||||
{"futmtrvh6j34t7s6yjdxfia6iwuyfzwh4k5eqfof5kfhoqk3xmi3qoqd.onion"},
|
||||
{NULL}
|
||||
};
|
||||
|
||||
|
||||
@@ -193,13 +193,16 @@ static void NotifyBlocksChanged(ClientModel *clientmodel)
|
||||
|
||||
static void NotifyNumConnectionsChanged(ClientModel *clientmodel, int newNumConnections)
|
||||
{
|
||||
// Too noisy: OutputDebugStringF("NotifyNumConnectionsChanged %i\n", newNumConnections);
|
||||
// Don't queue UI updates during shutdown - ClientModel may be destroyed
|
||||
// before Qt processes the queued invocation, causing use-after-free.
|
||||
if (fShutdown) return;
|
||||
QMetaObject::invokeMethod(clientmodel, "updateNumConnections", Qt::QueuedConnection,
|
||||
Q_ARG(int, newNumConnections));
|
||||
}
|
||||
|
||||
static void NotifyAlertChanged(ClientModel *clientmodel, const uint256 &hash, ChangeType status)
|
||||
{
|
||||
if (fShutdown) return;
|
||||
OutputDebugStringF("NotifyAlertChanged %s status=%i\n", hash.GetHex().c_str(), status);
|
||||
QMetaObject::invokeMethod(clientmodel, "updateAlert", Qt::QueuedConnection,
|
||||
Q_ARG(QString, QString::fromStdString(hash.GetHex())),
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include <set>
|
||||
|
||||
IntroDialog::IntroDialog(QWidget *parent) :
|
||||
QDialog(parent)
|
||||
{
|
||||
@@ -190,6 +192,24 @@ bool IntroDialog::pickDataDirectory()
|
||||
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) {
|
||||
@@ -275,3 +295,145 @@ bool IntroDialog::pickDataDirectory()
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void copyDirectoryRecursive(const boost::filesystem::path& src,
|
||||
const boost::filesystem::path& dst)
|
||||
{
|
||||
namespace fs = boost::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 = boost::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;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,13 @@ public:
|
||||
*/
|
||||
static bool pickDataDirectory();
|
||||
|
||||
/**
|
||||
* Migrate data directory contents from oldPath to newPath.
|
||||
* Returns true on success, false on failure.
|
||||
* Shows a progress dialog during the copy.
|
||||
*/
|
||||
static bool migrateDataDirectory(const QString& oldPath, const QString& newPath);
|
||||
|
||||
private slots:
|
||||
void on_browseButton_clicked();
|
||||
void on_defaultRadio_toggled(bool checked);
|
||||
|
||||
+202
-1
@@ -7,12 +7,21 @@
|
||||
#include "optionsmodel.h"
|
||||
#include "dialog_move_handler.h"
|
||||
|
||||
#include "init.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include <QDir>
|
||||
#include <QFileDialog>
|
||||
#include <QGroupBox>
|
||||
#include <QIntValidator>
|
||||
#include <QLocale>
|
||||
#include <QMessageBox>
|
||||
#include <QProcess>
|
||||
#include <QRegExp>
|
||||
#include <QRegExpValidator>
|
||||
#include <QSettings>
|
||||
|
||||
OptionsDialog::OptionsDialog(QWidget *parent) :
|
||||
QDialog(parent),
|
||||
@@ -21,12 +30,54 @@ OptionsDialog::OptionsDialog(QWidget *parent) :
|
||||
mapper(0),
|
||||
fRestartWarningDisplayed_Proxy(false),
|
||||
fRestartWarningDisplayed_Lang(false),
|
||||
fProxyIpValid(true)
|
||||
fProxyIpValid(true),
|
||||
dataDirPath(0),
|
||||
dataDirFreeSpaceLabel(0)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
setWindowFlags(Qt::CustomizeWindowHint | Qt::FramelessWindowHint | Qt::Window);
|
||||
ui->wCaption->installEventFilter(new DialogMoveHandler(this));
|
||||
|
||||
/* Data Directory section in Main tab */
|
||||
m_currentDataDir = QString::fromStdString(GetDataDir(false).string());
|
||||
m_pendingDataDir.clear();
|
||||
|
||||
QGroupBox *groupDataDir = new QGroupBox(tr("Data Directory"), this);
|
||||
groupDataDir->setStyleSheet(
|
||||
"QGroupBox { border: 1px solid #61280E; margin-top: 8px; padding-top: 16px; color: #f26522; }"
|
||||
"QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 3px; }");
|
||||
|
||||
QVBoxLayout *dataDirLayout = new QVBoxLayout(groupDataDir);
|
||||
|
||||
QHBoxLayout *dataDirPathLayout = new QHBoxLayout();
|
||||
dataDirPath = new QLineEdit(m_currentDataDir, groupDataDir);
|
||||
dataDirPath->setReadOnly(true);
|
||||
dataDirPath->setStyleSheet("QLineEdit { background-color: #1c1c1c; border: 1px solid #f26522; color: #f26522; padding: 2px; }");
|
||||
|
||||
QPushButton *dataDirBrowseButton = new QPushButton(tr("Browse..."), groupDataDir);
|
||||
dataDirBrowseButton->setStyleSheet(
|
||||
"QPushButton { background-color: #000; color: #f26522; border: 1px solid #f26522; padding: 2px 12px; min-height: 20px; }"
|
||||
"QPushButton:hover { background-color: #61280E; }"
|
||||
"QPushButton:pressed:flat { color: #000; background-color: #f26522; }");
|
||||
|
||||
dataDirPathLayout->addWidget(dataDirPath);
|
||||
dataDirPathLayout->addWidget(dataDirBrowseButton);
|
||||
dataDirLayout->addLayout(dataDirPathLayout);
|
||||
|
||||
dataDirFreeSpaceLabel = new QLabel(groupDataDir);
|
||||
dataDirFreeSpaceLabel->setStyleSheet("color: #999; font-size: 11px;");
|
||||
dataDirLayout->addWidget(dataDirFreeSpaceLabel);
|
||||
|
||||
// Insert into Main tab layout, before the vertical spacer (last item)
|
||||
QVBoxLayout *mainTabLayout = qobject_cast<QVBoxLayout*>(ui->tabWidget->widget(0)->layout());
|
||||
if (mainTabLayout) {
|
||||
int spacerIndex = mainTabLayout->count() - 1; // vertical spacer is last
|
||||
mainTabLayout->insertWidget(spacerIndex, groupDataDir);
|
||||
}
|
||||
|
||||
connect(dataDirBrowseButton, SIGNAL(clicked()), this, SLOT(on_dataDirBrowseButton_clicked()));
|
||||
updateDataDirFreeSpace();
|
||||
|
||||
/* Network elements init */
|
||||
#ifndef USE_UPNP
|
||||
ui->mapPortUpnp->setEnabled(false);
|
||||
@@ -188,6 +239,8 @@ void OptionsDialog::setSaveButtonState(bool fState)
|
||||
void OptionsDialog::on_okButton_clicked()
|
||||
{
|
||||
mapper->submit();
|
||||
if (handleDataDirChange())
|
||||
return; // restart flow handles closing
|
||||
accept();
|
||||
}
|
||||
|
||||
@@ -199,6 +252,7 @@ void OptionsDialog::on_cancelButton_clicked()
|
||||
void OptionsDialog::on_applyButton_clicked()
|
||||
{
|
||||
mapper->submit();
|
||||
handleDataDirChange();
|
||||
disableApplyButton();
|
||||
}
|
||||
|
||||
@@ -303,3 +357,150 @@ bool OptionsDialog::eventFilter(QObject *object, QEvent *event)
|
||||
}
|
||||
return QDialog::eventFilter(object, event);
|
||||
}
|
||||
|
||||
void OptionsDialog::on_dataDirBrowseButton_clicked()
|
||||
{
|
||||
QString dir = QFileDialog::getExistingDirectory(
|
||||
this, tr("Choose data directory"), m_currentDataDir);
|
||||
if (!dir.isEmpty() && dir != m_currentDataDir)
|
||||
{
|
||||
m_pendingDataDir = dir;
|
||||
dataDirPath->setText(dir);
|
||||
updateDataDirFreeSpace();
|
||||
enableApplyButton();
|
||||
}
|
||||
}
|
||||
|
||||
void OptionsDialog::updateDataDirFreeSpace()
|
||||
{
|
||||
namespace fs = boost::filesystem;
|
||||
QString path = dataDirPath->text();
|
||||
fs::path fsPath(path.toStdString());
|
||||
try {
|
||||
while (!fsPath.empty() && !fs::exists(fsPath))
|
||||
fsPath = fsPath.parent_path();
|
||||
if (!fsPath.empty()) {
|
||||
fs::space_info si = fs::space(fsPath);
|
||||
double freeGB = (double)si.available / (1024.0 * 1024.0 * 1024.0);
|
||||
dataDirFreeSpaceLabel->setText(
|
||||
tr("Free space: %1 GB").arg(QString::number(freeGB, 'f', 2)));
|
||||
} else {
|
||||
dataDirFreeSpaceLabel->setText(tr("Cannot determine free space"));
|
||||
}
|
||||
} catch (const fs::filesystem_error &) {
|
||||
dataDirFreeSpaceLabel->setText(tr("Cannot determine free space"));
|
||||
}
|
||||
}
|
||||
|
||||
quint64 OptionsDialog::calculateDirSize(const QString& path)
|
||||
{
|
||||
namespace fs = boost::filesystem;
|
||||
quint64 totalSize = 0;
|
||||
try {
|
||||
for (fs::recursive_directory_iterator it(path.toStdString()), end; it != end; ++it) {
|
||||
if (fs::is_regular_file(*it))
|
||||
totalSize += fs::file_size(*it);
|
||||
}
|
||||
} catch (...) {}
|
||||
return totalSize;
|
||||
}
|
||||
|
||||
bool OptionsDialog::handleDataDirChange()
|
||||
{
|
||||
if (m_pendingDataDir.isEmpty() || m_pendingDataDir == m_currentDataDir)
|
||||
return false;
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
fs::path destPath(m_pendingDataDir.toStdString());
|
||||
|
||||
// Check destination is writable
|
||||
try {
|
||||
fs::create_directories(destPath);
|
||||
} catch (const fs::filesystem_error& e) {
|
||||
QMessageBox::critical(this, tr("Error"),
|
||||
tr("Cannot create directory: %1").arg(QString::fromStdString(e.what())));
|
||||
m_pendingDataDir.clear();
|
||||
dataDirPath->setText(m_currentDataDir);
|
||||
updateDataDirFreeSpace();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check free space vs current data dir size
|
||||
quint64 dataDirSize = calculateDirSize(m_currentDataDir);
|
||||
try {
|
||||
fs::space_info si = fs::space(destPath);
|
||||
quint64 required = dataDirSize + (dataDirSize / 10); // 10% headroom
|
||||
if (si.available < required) {
|
||||
QMessageBox::critical(this, tr("Insufficient Space"),
|
||||
tr("The destination has %1 MB free but the data directory requires approximately %2 MB.")
|
||||
.arg(si.available / (1024*1024))
|
||||
.arg(required / (1024*1024)));
|
||||
m_pendingDataDir.clear();
|
||||
dataDirPath->setText(m_currentDataDir);
|
||||
updateDataDirFreeSpace();
|
||||
return false;
|
||||
}
|
||||
} catch (const fs::filesystem_error&) {
|
||||
// If we can't check space, proceed anyway
|
||||
}
|
||||
|
||||
// Save migration state to QSettings
|
||||
QSettings settings;
|
||||
settings.setValue("strDataDirPrevious", m_currentDataDir);
|
||||
settings.setValue("strDataDir", m_pendingDataDir);
|
||||
settings.setValue("fPendingDataDirMigration", true);
|
||||
|
||||
// Ask about restart
|
||||
QMessageBox msgBox(this);
|
||||
msgBox.setWindowFlags(Qt::FramelessWindowHint);
|
||||
msgBox.setWindowTitle(tr("Data Directory Changed"));
|
||||
msgBox.setText(tr("The data directory will be moved from:\n%1\n\nTo:\n%2\n\n"
|
||||
"This will happen when the wallet restarts.")
|
||||
.arg(m_currentDataDir).arg(m_pendingDataDir));
|
||||
msgBox.setIcon(QMessageBox::Information);
|
||||
msgBox.setIconPixmap(QPixmap(":/msgbox/information"));
|
||||
msgBox.setStyleSheet("QMessageBox { border: 2px solid #f26522; background-color: #000; color: #f26522; }");
|
||||
|
||||
QPushButton *restartBtn = msgBox.addButton(tr("Restart Now"), QMessageBox::AcceptRole);
|
||||
QPushButton *laterBtn = msgBox.addButton(tr("Later"), QMessageBox::RejectRole);
|
||||
|
||||
QString btnStyle =
|
||||
"QPushButton { background-color: #000; color: #f26522; border: 1px solid #f26522; "
|
||||
"min-width: 120px; max-width: 120px; max-height: 20px; min-height: 20px; }"
|
||||
"QPushButton:hover { background-color: #61280E; }"
|
||||
"QPushButton:pressed:flat { color: #000; background-color: #f26522; }";
|
||||
restartBtn->setStyleSheet(btnStyle);
|
||||
laterBtn->setStyleSheet(btnStyle);
|
||||
|
||||
msgBox.exec();
|
||||
|
||||
if (msgBox.clickedButton() == restartBtn) {
|
||||
performRestart();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void OptionsDialog::performRestart()
|
||||
{
|
||||
// Launch a new instance of ourselves
|
||||
QString exePath = QApplication::applicationFilePath();
|
||||
QStringList args = QApplication::arguments();
|
||||
args.removeFirst(); // remove argv[0]
|
||||
|
||||
// Remove any existing -datadir argument so the new instance
|
||||
// reads strDataDir from QSettings and performs migration
|
||||
QMutableStringListIterator it(args);
|
||||
while (it.hasNext()) {
|
||||
QString arg = it.next();
|
||||
if (arg.startsWith("-datadir") || arg.startsWith("/datadir"))
|
||||
it.remove();
|
||||
}
|
||||
|
||||
// Start new process detached so it survives our shutdown
|
||||
QProcess::startDetached(exePath, args);
|
||||
|
||||
// Close dialog and trigger wallet shutdown
|
||||
accept();
|
||||
StartShutdown();
|
||||
}
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
class QLineEdit;
|
||||
class QLabel;
|
||||
|
||||
namespace Ui {
|
||||
class OptionsDialog;
|
||||
}
|
||||
@@ -45,17 +48,29 @@ private slots:
|
||||
void updateDisplayUnit();
|
||||
void handleProxyIpValid(QValidatedLineEdit *object, bool fState);
|
||||
void applyTorDefaults(bool enabled);
|
||||
void on_dataDirBrowseButton_clicked();
|
||||
void updateDataDirFreeSpace();
|
||||
|
||||
signals:
|
||||
void proxyIpValid(QValidatedLineEdit *object, bool fValid);
|
||||
|
||||
private:
|
||||
bool handleDataDirChange();
|
||||
void performRestart();
|
||||
quint64 calculateDirSize(const QString& path);
|
||||
|
||||
Ui::OptionsDialog *ui;
|
||||
OptionsModel *model;
|
||||
MonitoredDataMapper *mapper;
|
||||
bool fRestartWarningDisplayed_Proxy;
|
||||
bool fRestartWarningDisplayed_Lang;
|
||||
bool fProxyIpValid;
|
||||
|
||||
// Data directory widgets (built programmatically)
|
||||
QLineEdit *dataDirPath;
|
||||
QLabel *dataDirFreeSpaceLabel;
|
||||
QString m_currentDataDir;
|
||||
QString m_pendingDataDir;
|
||||
};
|
||||
|
||||
#endif // OPTIONSDIALOG_H
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include "net.h"
|
||||
#include "addrman.h"
|
||||
#include "trianglesrpc.h"
|
||||
#include "alert.h"
|
||||
#include "wallet.h"
|
||||
@@ -199,3 +200,28 @@ Value sendalert(const Array& params, bool fHelp)
|
||||
result.push_back(Pair("nCancel", alert.nCancel));
|
||||
return result;
|
||||
}
|
||||
|
||||
Value getseedlist(const Array& params, bool fHelp)
|
||||
{
|
||||
if (fHelp || params.size() != 0)
|
||||
throw runtime_error(
|
||||
"getseedlist\n"
|
||||
"Returns known .onion peer addresses from the address manager.\n"
|
||||
"Used by the seed collector to build the dynamic seed list.");
|
||||
|
||||
vector<CAddress> vAddr = addrman.GetAddr();
|
||||
Array ret;
|
||||
|
||||
for (const CAddress& addr : vAddr) {
|
||||
if (!addr.IsTor())
|
||||
continue;
|
||||
|
||||
Object obj;
|
||||
obj.push_back(Pair("address", addr.ToStringIP()));
|
||||
obj.push_back(Pair("port", (int)addr.GetPort()));
|
||||
obj.push_back(Pair("lastseen", (boost::int64_t)addr.nTime));
|
||||
ret.push_back(obj);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
+230
-46
@@ -18,6 +18,7 @@
|
||||
#endif
|
||||
|
||||
#include "onion_v3.h"
|
||||
#include "tor_embedded.h"
|
||||
#include "tor_crypto_compat.h"
|
||||
#include "../util.h"
|
||||
#include "../net.h"
|
||||
@@ -59,6 +60,53 @@ extern CWallet* pwalletMain;
|
||||
CTorV3Manager* CTorV3Manager::instance = nullptr;
|
||||
static TorV3Config torV3Config;
|
||||
|
||||
static boost::filesystem::path GetBackendHiddenServiceDir(const std::string& torDataDir)
|
||||
{
|
||||
return boost::filesystem::path(torDataDir) / "hidden_service";
|
||||
}
|
||||
|
||||
static bool ReadTrimmedFirstLine(const boost::filesystem::path& path, std::string& valueOut)
|
||||
{
|
||||
valueOut.clear();
|
||||
|
||||
std::ifstream file(path.string().c_str());
|
||||
if (!file.is_open() || !std::getline(file, valueOut)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
while (!valueOut.empty()) {
|
||||
const char ch = valueOut[valueOut.size() - 1];
|
||||
if (ch != '\n' && ch != '\r' && ch != ' ' && ch != '\t') {
|
||||
break;
|
||||
}
|
||||
valueOut.erase(valueOut.size() - 1);
|
||||
}
|
||||
|
||||
return !valueOut.empty();
|
||||
}
|
||||
|
||||
static std::string GetEffectiveTorProxy()
|
||||
{
|
||||
proxyType proxy;
|
||||
if (GetProxy(NET_TOR, proxy)) {
|
||||
return proxy.first.ToStringIPPort();
|
||||
}
|
||||
|
||||
if (mapArgs.count("-tor") && mapArgs["-tor"] != "0") {
|
||||
CService torProxy(mapArgs["-tor"], (unsigned short)GetArg("-torsocks", 19099));
|
||||
if (torProxy.IsValid()) {
|
||||
return torProxy.ToStringIPPort();
|
||||
}
|
||||
}
|
||||
|
||||
std::string explicitProxy = GetArg("-torproxy", "");
|
||||
if (!explicitProxy.empty()) {
|
||||
return explicitProxy;
|
||||
}
|
||||
|
||||
return strprintf("127.0.0.1:%d", GetArg("-torsocks", 19099));
|
||||
}
|
||||
|
||||
// Utility function for proper base32 encoding (RFC 4648) - Tor variant
|
||||
std::string EncodeBase32Proper(const unsigned char* data, size_t len)
|
||||
{
|
||||
@@ -503,40 +551,95 @@ bool CTorV3Service::LoadFromPrivateKey(const std::string& privKey)
|
||||
|
||||
bool CTorV3Service::StartOnionService()
|
||||
{
|
||||
if (onionAddress.empty()) {
|
||||
printf("ERROR: No onion address generated\n");
|
||||
if (!torV3Config.enableTor || !torV3Config.enableHiddenService) {
|
||||
printf("ERROR: Tor hidden service backend is disabled\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Use data directory for Tor service files
|
||||
boost::filesystem::path serviceDir = GetDataDir() / "tor_data" / "triangles_v3";
|
||||
boost::filesystem::create_directories(serviceDir);
|
||||
return AttachToBackendService(torV3Config.torDataDirectory, port);
|
||||
}
|
||||
|
||||
// Create Tor configuration for hidden service
|
||||
std::string torrcContent = strprintf(
|
||||
"HiddenServiceDir %s\n"
|
||||
"HiddenServiceVersion 3\n"
|
||||
"HiddenServicePort %d 127.0.0.1:%d\n",
|
||||
serviceDir.string().c_str(), port, port
|
||||
);
|
||||
|
||||
// Write torrc file
|
||||
std::ofstream torrcFile((serviceDir / "torrc").string().c_str());
|
||||
if (torrcFile.is_open()) {
|
||||
torrcFile << torrcContent;
|
||||
torrcFile.close();
|
||||
bool CTorV3Service::AttachToBackendService(const std::string& torDataDir, int servicePort, int waitSeconds)
|
||||
{
|
||||
if (servicePort <= 0 || servicePort > 65535) {
|
||||
printf("ERROR: Invalid backend hidden service port: %d\n", servicePort);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write private key
|
||||
std::ofstream keyFile((serviceDir / "hs_ed25519_secret_key").string().c_str());
|
||||
if (keyFile.is_open()) {
|
||||
keyFile << "== ed25519v1-secret: type0 ==\n";
|
||||
keyFile << privateKey << "\n";
|
||||
keyFile.close();
|
||||
if (torDataDir.empty()) {
|
||||
printf("ERROR: Tor data directory is empty\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
port = servicePort;
|
||||
|
||||
const boost::filesystem::path serviceDir = GetBackendHiddenServiceDir(torDataDir);
|
||||
const boost::filesystem::path hostnamePath = serviceDir / "hostname";
|
||||
|
||||
std::string backendOnion;
|
||||
for (int waited = 0; waited <= waitSeconds; ++waited) {
|
||||
if (boost::filesystem::exists(hostnamePath) &&
|
||||
ReadTrimmedFirstLine(hostnamePath, backendOnion)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (waited == waitSeconds) {
|
||||
printf("ERROR: Timed out waiting for Tor hidden service hostname at %s\n",
|
||||
hostnamePath.string().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fShutdown) {
|
||||
printf("ERROR: Shutdown requested while waiting for Tor hidden service hostname\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
MilliSleep(1000);
|
||||
}
|
||||
|
||||
if (!ValidateOnionAddress(backendOnion)) {
|
||||
printf("ERROR: Tor backend produced invalid onion address: %s\n", backendOnion.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!onionAddress.empty() && onionAddress != backendOnion) {
|
||||
printf("WARNING: Replacing wallet-managed onion address %s with Tor backend address %s\n",
|
||||
onionAddress.c_str(), backendOnion.c_str());
|
||||
}
|
||||
|
||||
onionAddress = backendOnion;
|
||||
isActive = true;
|
||||
printf("Started V3 onion service on %s:%d\n", onionAddress.c_str(), port);
|
||||
|
||||
if (pwalletMain) {
|
||||
CWalletDB walletdb(pwalletMain->strWalletFile);
|
||||
walletdb.WriteSetting("tor_v3_onion_address", onionAddress);
|
||||
|
||||
// Back up the Tor-generated secret key to wallet.dat so the onion
|
||||
// identity survives deletion of the tor_data directory.
|
||||
boost::filesystem::path secretKeyPath = serviceDir / "hs_ed25519_secret_key";
|
||||
if (boost::filesystem::exists(secretKeyPath)) {
|
||||
std::ifstream keyFile(secretKeyPath.string().c_str(), std::ios::binary);
|
||||
if (keyFile.is_open()) {
|
||||
std::vector<unsigned char> keyData(
|
||||
(std::istreambuf_iterator<char>(keyFile)),
|
||||
std::istreambuf_iterator<char>());
|
||||
keyFile.close();
|
||||
|
||||
if (keyData.size() == 96) {
|
||||
walletdb.WriteSetting("tor_v3_hs_secret_key_backup", keyData);
|
||||
printf("Backed up Tor hidden service secret key to wallet (%d bytes)\n",
|
||||
(int)keyData.size());
|
||||
} else {
|
||||
printf("WARNING: hs_ed25519_secret_key has unexpected size %d (expected 96), not backing up\n",
|
||||
(int)keyData.size());
|
||||
}
|
||||
|
||||
OPENSSL_cleanse(keyData.data(), keyData.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
printf("Attached V3 onion service to Tor backend at %s:%d\n", onionAddress.c_str(), port);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1142,20 +1245,10 @@ void CTorV3Manager::ShutdownTor()
|
||||
bool CTorV3Manager::CreateWalletHiddenService(int port)
|
||||
{
|
||||
CTorV3Service* service = new CTorV3Service();
|
||||
|
||||
// Try to load existing service first
|
||||
if (!service->LoadFromWallet()) {
|
||||
// Generate new service
|
||||
if (!service->GenerateV3Service(port)) {
|
||||
delete service;
|
||||
return false;
|
||||
}
|
||||
service->SaveToWallet();
|
||||
}
|
||||
|
||||
if (service->StartOnionService()) {
|
||||
|
||||
if (service->AttachToBackendService(torDataDir, port)) {
|
||||
services[port] = service;
|
||||
printf("Wallet hidden service created: %s\n", service->GetOnionAddress().c_str());
|
||||
printf("Wallet hidden service attached: %s\n", service->GetOnionAddress().c_str());
|
||||
|
||||
// If seeder mode is enabled, automatically register as seeder
|
||||
if (torV3Config.enableSeederMode) {
|
||||
@@ -1968,12 +2061,12 @@ void CTorV3Manager::UpdateDiscoveryStats(int connected, int attempted)
|
||||
bool LoadTorV3Config()
|
||||
{
|
||||
// Tor V3 identity is innate to Triangles — enabled by default
|
||||
torV3Config.enableTor = GetBoolArg("-tor", true);
|
||||
torV3Config.enableHiddenService = GetBoolArg("-torhiddenservice", true);
|
||||
torV3Config.enableTor = !GetBoolArg("-notor", false);
|
||||
torV3Config.enableHiddenService = torV3Config.enableTor && GetBoolArg("-torhiddenservice", true);
|
||||
torV3Config.enableSeederMode = GetBoolArg("-torseeder", false);
|
||||
torV3Config.hiddenServicePort = GetArg("-torhiddenserviceport", GetDefaultPort());
|
||||
torV3Config.hiddenServicePort = GetArg("-torhsport", GetListenPort());
|
||||
torV3Config.torDataDirectory = GetArg("-tordatadir", (GetDataDir() / "tor_data").string());
|
||||
torV3Config.socksProxy = GetArg("-torproxy", "127.0.0.1:9050");
|
||||
torV3Config.socksProxy = GetEffectiveTorProxy();
|
||||
torV3Config.maxConnections = GetArg("-tormaxconnections", 8);
|
||||
|
||||
printf("Loaded Tor V3 configuration: enabled=%s, hidden_service=%s, seeder=%s, proxy=%s\n",
|
||||
@@ -2093,15 +2186,106 @@ void CTorV3Manager::RequestSeederListFromPeer(const std::string& peerAddress)
|
||||
// Schedule periodic seeder re-announcements
|
||||
void CTorV3Manager::ScheduleSeederReannouncement()
|
||||
{
|
||||
// This would typically be handled by a timer or scheduler
|
||||
// For now, we'll just update the last announcement time
|
||||
if (pwalletMain) {
|
||||
CWalletDB walletdb(pwalletMain->strWalletFile);
|
||||
walletdb.WriteSetting("seeder_last_announcement", (int64_t)GetTime());
|
||||
printf("Scheduled seeder re-announcement\n");
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Background thread: Tor health monitoring + seeder maintenance
|
||||
// ---------------------------------------------------------------------------
|
||||
void ThreadTorMaintenance(void* parg)
|
||||
{
|
||||
RenameThread("Triangles-tormaint");
|
||||
printf("Tor maintenance thread started\n");
|
||||
|
||||
int restartBackoffSec = 30;
|
||||
int64_t lastSeederMaint = GetTime();
|
||||
static const int SEEDER_INTERVAL = 1800; // 30 minutes
|
||||
|
||||
while (!fShutdown)
|
||||
{
|
||||
// Sleep in short intervals so the thread exits promptly on shutdown
|
||||
for (int i = 0; i < 60 && !fShutdown; i++)
|
||||
MilliSleep(500);
|
||||
if (fShutdown) break;
|
||||
|
||||
// --- Tor health check & auto-restart ---
|
||||
if (!CTorEmbedded::GetInstance()->IsRunning())
|
||||
{
|
||||
printf("WARNING: Tor process is no longer running, attempting restart...\n");
|
||||
|
||||
if (StartEmbeddedTor())
|
||||
{
|
||||
printf("Tor restarted successfully\n");
|
||||
restartBackoffSec = 30;
|
||||
|
||||
// Re-attach the hidden service identity
|
||||
TorV3Config& torConfig = GetTorV3Config();
|
||||
std::string torDataPath = CTorEmbedded::GetInstance()->GetDataDir();
|
||||
if (torDataPath.empty())
|
||||
torDataPath = torConfig.torDataDirectory;
|
||||
|
||||
torConfig.enableTor = true;
|
||||
torConfig.enableHiddenService = CTorEmbedded::GetInstance()->IsHiddenServiceEnabled();
|
||||
torConfig.hiddenServicePort = CTorEmbedded::GetInstance()->GetHiddenServicePort();
|
||||
torConfig.torDataDirectory = torDataPath;
|
||||
|
||||
if (torConfig.enableHiddenService && InitTorV3())
|
||||
{
|
||||
std::string onionAddr = CTorV3Manager::GetInstance()->GetWalletOnionAddress();
|
||||
if (!onionAddr.empty())
|
||||
{
|
||||
AddLocal(CService(onionAddr, torConfig.hiddenServicePort), LOCAL_MANUAL);
|
||||
printf("Re-registered Tor V3 identity after restart: %s\n", onionAddr.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("WARNING: Tor restart failed, retrying in %d seconds\n", restartBackoffSec);
|
||||
for (int i = 0; i < restartBackoffSec * 2 && !fShutdown; i++)
|
||||
MilliSleep(500);
|
||||
if (restartBackoffSec < 300)
|
||||
restartBackoffSec *= 2;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// --- Seeder maintenance (every 30 minutes) ---
|
||||
TorV3Config& cfg = GetTorV3Config();
|
||||
if (cfg.enableSeederMode && (GetTime() - lastSeederMaint) >= SEEDER_INTERVAL)
|
||||
{
|
||||
CTorV3Manager* mgr = CTorV3Manager::GetInstance();
|
||||
std::string ownAddr = mgr->GetWalletOnionAddress();
|
||||
|
||||
if (!ownAddr.empty())
|
||||
{
|
||||
// Re-announce ourselves as a seeder to all peers
|
||||
{
|
||||
LOCK(cs_vNodes);
|
||||
for (CNode* pnode : vNodes)
|
||||
{
|
||||
try {
|
||||
pnode->PushMessage("seeder", ownAddr, cfg.hiddenServicePort);
|
||||
} catch (...) {}
|
||||
}
|
||||
}
|
||||
printf("Seeder re-announcement sent to %d peers\n", (int)vNodes.size());
|
||||
}
|
||||
|
||||
// Refresh our knowledge of other seeders
|
||||
mgr->RequestSeederListFromPeers();
|
||||
|
||||
mgr->ScheduleSeederReannouncement();
|
||||
lastSeederMaint = GetTime();
|
||||
}
|
||||
}
|
||||
|
||||
printf("Tor maintenance thread exited\n");
|
||||
}
|
||||
|
||||
// Global functions
|
||||
bool InitTorV3()
|
||||
{
|
||||
@@ -2111,4 +2295,4 @@ bool InitTorV3()
|
||||
void ShutdownTorV3()
|
||||
{
|
||||
CTorV3Manager::GetInstance()->ShutdownTor();
|
||||
}
|
||||
}
|
||||
|
||||
+6
-2
@@ -34,7 +34,10 @@ public:
|
||||
|
||||
// Start the onion service
|
||||
bool StartOnionService();
|
||||
|
||||
|
||||
// Attach to the hidden service managed by the running Tor backend
|
||||
bool AttachToBackendService(const std::string& torDataDir, int servicePort, int waitSeconds = 30);
|
||||
|
||||
// Stop the onion service
|
||||
bool StopService();
|
||||
|
||||
@@ -169,5 +172,6 @@ void ShutdownTorV3();
|
||||
TorV3Config& GetTorV3Config();
|
||||
bool SaveTorV3Config();
|
||||
bool LoadTorV3Config();
|
||||
void ThreadTorMaintenance(void* parg);
|
||||
|
||||
#endif // TRIANGLES_TOR_ONION_V3_H
|
||||
#endif // TRIANGLES_TOR_ONION_V3_H
|
||||
|
||||
+35
-23
@@ -52,6 +52,7 @@ CTorEmbedded::CTorEmbedded()
|
||||
: running(false)
|
||||
, socksPort(19099)
|
||||
, hiddenServicePort(24112)
|
||||
, hiddenServiceEnabled(true)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -109,20 +110,24 @@ static void TorThreadFunc(std::vector<std::string> argv_strings)
|
||||
CTorEmbedded::GetInstance()->SetRunning(false);
|
||||
}
|
||||
|
||||
bool CTorEmbedded::Start(int socks, int hsPort)
|
||||
bool CTorEmbedded::Start(int socks, int hsPort, bool enableHiddenService)
|
||||
{
|
||||
if (running.load()) return true;
|
||||
|
||||
socksPort = socks;
|
||||
hiddenServicePort = hsPort;
|
||||
hiddenServiceEnabled = enableHiddenService;
|
||||
hiddenServicePort = hiddenServiceEnabled ? hsPort : 0;
|
||||
onionHostname.clear();
|
||||
|
||||
// Prepare Tor data directory under the wallet's data dir
|
||||
torDataDir = (::GetDataDir() / "tor_data").string();
|
||||
fs::create_directories(torDataDir);
|
||||
|
||||
// Hidden service directory
|
||||
std::string hsDir = (fs::path(torDataDir) / "hidden_service").string();
|
||||
fs::create_directories(hsDir);
|
||||
std::string hsDir;
|
||||
if (hiddenServiceEnabled) {
|
||||
hsDir = (fs::path(torDataDir) / "hidden_service").string();
|
||||
fs::create_directories(hsDir);
|
||||
}
|
||||
|
||||
// Build the argv for tor_run_main
|
||||
std::vector<std::string> argv;
|
||||
@@ -131,12 +136,14 @@ bool CTorEmbedded::Start(int socks, int hsPort)
|
||||
argv.push_back(std::to_string(socksPort));
|
||||
argv.push_back("--DataDirectory");
|
||||
argv.push_back(torDataDir);
|
||||
argv.push_back("--HiddenServiceDir");
|
||||
argv.push_back(hsDir);
|
||||
argv.push_back("--HiddenServiceVersion");
|
||||
argv.push_back("3");
|
||||
argv.push_back("--HiddenServicePort");
|
||||
argv.push_back(std::to_string(hiddenServicePort) + " 127.0.0.1:" + std::to_string(hiddenServicePort));
|
||||
if (hiddenServiceEnabled) {
|
||||
argv.push_back("--HiddenServiceDir");
|
||||
argv.push_back(hsDir);
|
||||
argv.push_back("--HiddenServiceVersion");
|
||||
argv.push_back("3");
|
||||
argv.push_back("--HiddenServicePort");
|
||||
argv.push_back(std::to_string(hiddenServicePort) + " 127.0.0.1:" + std::to_string(hiddenServicePort));
|
||||
}
|
||||
argv.push_back("--AvoidDiskWrites");
|
||||
argv.push_back("1");
|
||||
argv.push_back("--Log");
|
||||
@@ -175,13 +182,15 @@ bool CTorEmbedded::Start(int socks, int hsPort)
|
||||
printf("Embedded Tor SOCKS proxy ready on port %d (took %ds)\n", socksPort, i + 1);
|
||||
|
||||
// Read .onion hostname if available
|
||||
fs::path hostnameFile = fs::path(hsDir) / "hostname";
|
||||
if (fs::exists(hostnameFile)) {
|
||||
std::ifstream f(hostnameFile.string().c_str());
|
||||
if (f.is_open())
|
||||
std::getline(f, onionHostname);
|
||||
if (!onionHostname.empty())
|
||||
printf("Tor hidden service: %s\n", onionHostname.c_str());
|
||||
if (hiddenServiceEnabled) {
|
||||
fs::path hostnameFile = fs::path(hsDir) / "hostname";
|
||||
if (fs::exists(hostnameFile)) {
|
||||
std::ifstream f(hostnameFile.string().c_str());
|
||||
if (f.is_open())
|
||||
std::getline(f, onionHostname);
|
||||
if (!onionHostname.empty())
|
||||
printf("Tor hidden service: %s\n", onionHostname.c_str());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -219,14 +228,16 @@ void CTorEmbedded::Stop()
|
||||
|
||||
#include "tor_process.h"
|
||||
|
||||
bool CTorEmbedded::Start(int socks, int hsPort)
|
||||
bool CTorEmbedded::Start(int socks, int hsPort, bool enableHiddenService)
|
||||
{
|
||||
printf("Embedded Tor not compiled in. Using external Tor process.\n");
|
||||
// Delegate to the external process manager
|
||||
socksPort = socks;
|
||||
hiddenServicePort = hsPort;
|
||||
hiddenServiceEnabled = enableHiddenService;
|
||||
hiddenServicePort = hiddenServiceEnabled ? hsPort : 0;
|
||||
onionHostname.clear();
|
||||
torDataDir = (::GetDataDir() / "tor_data").string();
|
||||
running.store(StartTorProcess(torDataDir, socksPort, hiddenServicePort));
|
||||
running.store(StartTorProcess(torDataDir, socksPort, hiddenServicePort, hiddenServiceEnabled));
|
||||
return running.load();
|
||||
}
|
||||
|
||||
@@ -249,10 +260,11 @@ bool StartEmbeddedTor()
|
||||
return false;
|
||||
}
|
||||
|
||||
bool enableHiddenService = GetBoolArg("-torhiddenservice", true);
|
||||
int socksPort = GetArg("-torsocks", 19099);
|
||||
int hsPort = GetArg("-torhsport", GetListenPort());
|
||||
int hsPort = enableHiddenService ? GetArg("-torhsport", GetListenPort()) : 0;
|
||||
|
||||
return CTorEmbedded::GetInstance()->Start(socksPort, hsPort);
|
||||
return CTorEmbedded::GetInstance()->Start(socksPort, hsPort, enableHiddenService);
|
||||
}
|
||||
|
||||
void StopEmbeddedTor()
|
||||
|
||||
@@ -16,6 +16,7 @@ private:
|
||||
std::atomic<bool> running;
|
||||
int socksPort;
|
||||
int hiddenServicePort;
|
||||
bool hiddenServiceEnabled;
|
||||
std::string torDataDir;
|
||||
std::string onionHostname;
|
||||
|
||||
@@ -26,7 +27,7 @@ public:
|
||||
~CTorEmbedded();
|
||||
|
||||
// Start Tor in a background thread (blocks that thread until shutdown)
|
||||
bool Start(int socksPort = 19099, int hsPort = 24112);
|
||||
bool Start(int socksPort = 19099, int hsPort = 24112, bool enableHiddenService = true);
|
||||
|
||||
// Request Tor to shut down
|
||||
void Stop();
|
||||
@@ -45,6 +46,7 @@ public:
|
||||
|
||||
// Get the hidden service port
|
||||
int GetHiddenServicePort() const { return hiddenServicePort; }
|
||||
bool IsHiddenServiceEnabled() const { return hiddenServiceEnabled; }
|
||||
};
|
||||
|
||||
// Global init/shutdown hooks (called from init.cpp)
|
||||
|
||||
+28
-17
@@ -48,6 +48,7 @@ CTorProcess* CTorProcess::GetInstance()
|
||||
CTorProcess::CTorProcess()
|
||||
: socksPort(19099)
|
||||
, hiddenServicePort(24112)
|
||||
, hiddenServiceEnabled(true)
|
||||
, running(false)
|
||||
#ifdef WIN32
|
||||
, hProcess(NULL)
|
||||
@@ -198,11 +199,13 @@ bool CTorProcess::WriteTorrc()
|
||||
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";
|
||||
if (hiddenServiceEnabled) {
|
||||
// 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";
|
||||
@@ -213,15 +216,21 @@ bool CTorProcess::WriteTorrc()
|
||||
|
||||
torrc.close();
|
||||
|
||||
printf("Wrote torrc to %s (SOCKS %d, HS port %d)\n",
|
||||
torrcPath.c_str(), socksPort, hiddenServicePort);
|
||||
if (hiddenServiceEnabled) {
|
||||
printf("Wrote torrc to %s (SOCKS %d, HS port %d)\n",
|
||||
torrcPath.c_str(), socksPort, hiddenServicePort);
|
||||
} else {
|
||||
printf("Wrote torrc to %s (SOCKS %d, hidden service disabled)\n",
|
||||
torrcPath.c_str(), socksPort);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CTorProcess::Start(const std::string& dataDir, int socks, int hsPort)
|
||||
bool CTorProcess::Start(const std::string& dataDir, int socks, int hsPort, bool enableHiddenService)
|
||||
{
|
||||
socksPort = socks;
|
||||
hiddenServicePort = hsPort;
|
||||
hiddenServiceEnabled = enableHiddenService;
|
||||
hiddenServicePort = hiddenServiceEnabled ? hsPort : 0;
|
||||
torDataDir = dataDir;
|
||||
|
||||
// Check if something is already listening on our SOCKS port
|
||||
@@ -314,12 +323,14 @@ bool CTorProcess::Start(const std::string& dataDir, int socks, int hsPort)
|
||||
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());
|
||||
if (hiddenServiceEnabled) {
|
||||
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;
|
||||
@@ -402,9 +413,9 @@ std::string CTorProcess::GetSocksProxy() const
|
||||
}
|
||||
|
||||
// Global convenience functions
|
||||
bool StartTorProcess(const std::string& dataDir, int socksPort, int hsPort)
|
||||
bool StartTorProcess(const std::string& dataDir, int socksPort, int hsPort, bool enableHiddenService)
|
||||
{
|
||||
return CTorProcess::GetInstance()->Start(dataDir, socksPort, hsPort);
|
||||
return CTorProcess::GetInstance()->Start(dataDir, socksPort, hsPort, enableHiddenService);
|
||||
}
|
||||
|
||||
void StopTorProcess()
|
||||
|
||||
@@ -21,6 +21,7 @@ private:
|
||||
std::string torrcPath;
|
||||
int socksPort;
|
||||
int hiddenServicePort;
|
||||
bool hiddenServiceEnabled;
|
||||
bool running;
|
||||
|
||||
#ifdef WIN32
|
||||
@@ -45,7 +46,7 @@ public:
|
||||
|
||||
// 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);
|
||||
bool Start(const std::string& dataDir, int socksPort = 19099, int hsPort = 24112, bool enableHiddenService = true);
|
||||
|
||||
// Stop the Tor process
|
||||
void Stop();
|
||||
@@ -64,7 +65,7 @@ public:
|
||||
};
|
||||
|
||||
// Global convenience functions
|
||||
bool StartTorProcess(const std::string& dataDir, int socksPort = 19099, int hsPort = 24112);
|
||||
bool StartTorProcess(const std::string& dataDir, int socksPort = 19099, int hsPort = 24112, bool enableHiddenService = true);
|
||||
void StopTorProcess();
|
||||
|
||||
#endif // TRIANGLES_TOR_PROCESS_H
|
||||
|
||||
+18
-2
@@ -253,6 +253,7 @@ static const CRPCCommand vRPCCommands[] =
|
||||
{ "getblockchaininfo", &getblockchaininfo, true, false },
|
||||
{ "getwalletinfo", &getwalletinfo, true, false },
|
||||
{ "getnetworkinfo", &getnetworkinfo, true, false },
|
||||
{ "getseedlist", &getseedlist, true, false },
|
||||
{ "gettxoutsetinfo", &gettxoutsetinfo, true, false },
|
||||
{ "estimatefee", &estimatefee, true, false },
|
||||
{ "getaddressbalance", &getaddressbalance, true, false },
|
||||
@@ -946,9 +947,24 @@ void ThreadRPCServer2(void* parg)
|
||||
|
||||
vnThreadsRunning[THREAD_RPCLISTENER]--;
|
||||
while (!fShutdown)
|
||||
io_service.run_one();
|
||||
{
|
||||
// Use poll_one + sleep instead of blocking run_one so the thread
|
||||
// remains responsive to fShutdown and can exit promptly.
|
||||
if (!io_service.poll_one())
|
||||
{
|
||||
io_service.restart();
|
||||
MilliSleep(50);
|
||||
}
|
||||
}
|
||||
vnThreadsRunning[THREAD_RPCLISTENER]++;
|
||||
StopRequests();
|
||||
|
||||
// Safely shut down: close acceptors, then drain any remaining handlers
|
||||
try {
|
||||
StopRequests();
|
||||
} catch (...) {
|
||||
// Absorb bad_weak_ptr or other exceptions from stale tracked slots
|
||||
}
|
||||
io_service.poll(); // process cancellation callbacks so shared_ptrs are released
|
||||
}
|
||||
|
||||
class JSONRequest
|
||||
|
||||
@@ -149,6 +149,7 @@ extern std::vector<unsigned char> ParseHexO(const json_spirit::Object& o, std::s
|
||||
extern json_spirit::Value getconnectioncount(const json_spirit::Array& params, bool fHelp); // in rpcnet.cpp
|
||||
extern json_spirit::Value getpeerinfo(const json_spirit::Array& params, bool fHelp);
|
||||
extern json_spirit::Value getnetworkinfo(const json_spirit::Array& params, bool fHelp);
|
||||
extern json_spirit::Value getseedlist(const json_spirit::Array& params, bool fHelp);
|
||||
extern json_spirit::Value getwalletinfo(const json_spirit::Array& params, bool fHelp);
|
||||
extern json_spirit::Value dumpwallet(const json_spirit::Array& params, bool fHelp);
|
||||
extern json_spirit::Value importwallet(const json_spirit::Array& params, bool fHelp);
|
||||
|
||||
+46
-35
@@ -486,7 +486,8 @@ void CWallet::WalletUpdateSpent(const CTransaction &tx, bool fBlock)
|
||||
printf("WalletUpdateSpent found spent coin %s TRI %s\n", FormatMoney(wtx.GetCredit()).c_str(), wtx.GetHash().ToString().c_str());
|
||||
wtx.MarkSpent(txin.prevout.n);
|
||||
wtx.WriteToDisk();
|
||||
NotifyTransactionChanged(this, txin.prevout.hash, CT_UPDATED);
|
||||
if (!IsInitialBlockDownload())
|
||||
NotifyTransactionChanged(this, txin.prevout.hash, CT_UPDATED);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -503,7 +504,8 @@ void CWallet::WalletUpdateSpent(const CTransaction &tx, bool fBlock)
|
||||
{
|
||||
wtx.MarkUnspent(&txout - &tx.vout[0]);
|
||||
wtx.WriteToDisk();
|
||||
NotifyTransactionChanged(this, hash, CT_UPDATED);
|
||||
if (!IsInitialBlockDownload())
|
||||
NotifyTransactionChanged(this, hash, CT_UPDATED);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -634,8 +636,9 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn)
|
||||
// since AddToWallet is called directly for self-originating transactions, check for consumption of own coins
|
||||
WalletUpdateSpent(wtx, (wtxIn.hashBlock != 0));
|
||||
|
||||
// Notify UI of new or updated transaction
|
||||
NotifyTransactionChanged(this, hash, fInsertedNew ? CT_NEW : CT_UPDATED);
|
||||
// Notify UI of new or updated transaction (skip during IBD to avoid flooding the event loop)
|
||||
if (!IsInitialBlockDownload())
|
||||
NotifyTransactionChanged(this, hash, fInsertedNew ? CT_NEW : CT_UPDATED);
|
||||
|
||||
// notify an external script when a wallet transaction comes in or is updated
|
||||
std::string strCmd = GetArg("-walletnotify", "");
|
||||
@@ -956,39 +959,47 @@ int CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate)
|
||||
int ret = 0;
|
||||
|
||||
CBlockIndex* pindex = pindexStart;
|
||||
int nScanned = 0;
|
||||
int nTotal = nBestHeight - (pindexStart ? pindexStart->nHeight : 0);
|
||||
if (nTotal < 1) nTotal = 1;
|
||||
int64_t nLastProgressTime = GetTimeMillis();
|
||||
|
||||
// Cache wallet birthday outside the loop (only written during key import)
|
||||
int64_t nBirthTime = nTimeFirstKey;
|
||||
|
||||
while (pindex)
|
||||
{
|
||||
LOCK(cs_wallet);
|
||||
int nScanned = 0;
|
||||
int nTotal = nBestHeight - (pindexStart ? pindexStart->nHeight : 0);
|
||||
if (nTotal < 1) nTotal = 1;
|
||||
while (pindex)
|
||||
if (fShutdown)
|
||||
break;
|
||||
|
||||
++nScanned;
|
||||
// Report progress every 500ms to keep UI responsive
|
||||
int64_t nNow = GetTimeMillis();
|
||||
if (nNow - nLastProgressTime > 500)
|
||||
{
|
||||
if (fShutdown)
|
||||
break;
|
||||
|
||||
// Report progress every 10000 blocks to keep UI responsive
|
||||
if (++nScanned % 10000 == 0)
|
||||
{
|
||||
int nPercent = (nScanned * 100) / nTotal;
|
||||
uiInterface.InitMessage(strprintf(_("Rescanning... %d%%"), nPercent));
|
||||
}
|
||||
|
||||
// no need to read and scan block, if block was created before
|
||||
// our wallet birthday (as adjusted for block time variability)
|
||||
if (nTimeFirstKey && (pindex->nTime < (nTimeFirstKey - 7200))) {
|
||||
pindex = pindex->pnext;
|
||||
continue;
|
||||
}
|
||||
|
||||
CBlock block;
|
||||
block.ReadFromDisk(pindex, true);
|
||||
for (CTransaction& tx : block.vtx)
|
||||
{
|
||||
if (AddToWalletIfInvolvingMe(tx, &block, fUpdate))
|
||||
ret++;
|
||||
}
|
||||
pindex = pindex->pnext;
|
||||
nLastProgressTime = nNow;
|
||||
int nPercent = (nScanned * 100) / nTotal;
|
||||
uiInterface.InitMessage(strprintf(_("Rescanning... %d%%"), nPercent));
|
||||
}
|
||||
|
||||
// no need to read and scan block, if block was created before
|
||||
// our wallet birthday (as adjusted for block time variability)
|
||||
if (nBirthTime && (pindex->nTime < (nBirthTime - 7200))) {
|
||||
pindex = pindex->pnext;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Read block from disk WITHOUT holding wallet lock
|
||||
CBlock block;
|
||||
block.ReadFromDisk(pindex, true);
|
||||
|
||||
// AddToWalletIfInvolvingMe acquires cs_wallet internally
|
||||
for (CTransaction& tx : block.vtx)
|
||||
{
|
||||
if (AddToWalletIfInvolvingMe(tx, &block, fUpdate))
|
||||
ret++;
|
||||
}
|
||||
pindex = pindex->pnext;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -2775,7 +2786,7 @@ void CWallet::UpdatedTransaction(const uint256 &hashTx)
|
||||
LOCK(cs_wallet);
|
||||
// Only notify UI if this transaction is in this wallet
|
||||
map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(hashTx);
|
||||
if (mi != mapWallet.end())
|
||||
if (mi != mapWallet.end() && !IsInitialBlockDownload())
|
||||
NotifyTransactionChanged(this, hashTx, CT_UPDATED);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user