diff --git a/CMakeLists.txt b/CMakeLists.txt index 3836949..272fce1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ if(POLICY CMP0167) endif() project(Triangles - VERSION 5.7.9.0 + VERSION 5.8.0.0 DESCRIPTION "Cryptographic Triangles Wallet" LANGUAGES C CXX ) diff --git a/README.md b/README.md index 15c51b4..2e32ea5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Cryptographic Triangles (TRI) - v5.1.5 +# Cryptographic Triangles (TRI) Triangles is a privacy-focused cryptocurrency featuring Proof-of-Stake consensus, Tor v3 onion routing, and built-in encrypted messaging. Originally launched in July 2014, the chain was revived in March 2026 after being frozen since December 2022. @@ -16,7 +16,7 @@ Triangles is a privacy-focused cryptocurrency featuring Proof-of-Stake consensus |----------|-------| | Algorithm | Hash9 (PoW blocks 0-9000), PoS from block 9001 | | Block Time | ~120 seconds | -| Max Supply | 222,222 TRI | +| Max Supply | 2,222,222 TRI | | PoS Reward | 33% annual, coin-age based | | P2P Port | 24112 | | RPC Port | 19112 | @@ -24,11 +24,7 @@ Triangles is a privacy-focused cryptocurrency featuring Proof-of-Stake consensus ## Network Status -The Triangles network is live with seed nodes operating on both clearnet and Tor: - -**Clearnet Seeds:** -- `194.233.88.206:24112` -- `74.208.167.19:24112` +The Triangles network operates exclusively over Tor for privacy: **Tor v3 Seeds:** - `gxvrhv3qitnc6kobrhsrse46bmcfitnybapor3or3oczzuxn6hfzxyid.onion:24112` @@ -40,32 +36,46 @@ The Triangles network is live with seed nodes operating on both clearnet and Tor ## Building from Source +Triangles uses CMake. All platforms follow the same build pattern. + +### Dependencies + +| Dependency | Minimum Version | +|------------|----------------| +| CMake | 3.16+ | +| C++ compiler | C++17 support | +| OpenSSL | 3.x | +| Boost | 1.90+ | +| Berkeley DB | 5.3 (with C++ bindings) | +| libevent | 2.x | +| LevelDB | bundled | + ### Linux (Ubuntu 24.04 / Debian 12+) Install dependencies: ```bash -sudo apt-get install -y build-essential libboost-all-dev libssl-dev \ - libdb5.3++-dev libevent-dev zlib1g-dev libminiupnpc-dev +sudo apt-get install -y build-essential cmake ninja-build \ + libboost-all-dev libssl-dev libdb5.3++-dev libevent-dev \ + zlib1g-dev libminiupnpc-dev ``` -Build the daemon: +For the Qt wallet, also install: ```bash -cd src/leveldb && make libleveldb.a libmemenv.a && cd .. -make -j$(nproc) -f makefile.unix USE_UPNP=0 -strip trianglesd +sudo apt-get install -y qtbase5-dev qt5-qmake libqrencode-dev ``` -Run the unit test suite: +Build: ```bash -make -C src -f makefile.unix test +cmake -B build -G Ninja -DBUILD_QT=ON +cmake --build build ``` ### Linux (AlmaLinux 9 / RHEL 9) Install dependencies: ```bash -sudo dnf install -y gcc-c++ make boost-devel openssl-devel libevent-devel \ - zlib-devel miniupnpc-devel +sudo dnf install -y gcc-c++ cmake ninja-build boost-devel openssl-devel \ + libevent-devel zlib-devel miniupnpc-devel ``` BDB 5.3 C++ bindings must be built from source on RHEL-based systems (the `libdb-devel` package does not include C++ headers). Download BDB 5.3.28 from Oracle and build with `--enable-cxx`. @@ -76,17 +86,27 @@ Then build as above. Open an MSYS2 MinGW64 shell and install: ```bash -pacman -S mingw-w64-x86_64-boost mingw-w64-x86_64-openssl \ +pacman -S mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja \ + mingw-w64-x86_64-boost mingw-w64-x86_64-openssl \ mingw-w64-x86_64-db mingw-w64-x86_64-miniupnpc \ - mingw-w64-x86_64-qt5-base mingw-w64-x86_64-qrencode + mingw-w64-x86_64-qt5-base mingw-w64-x86_64-qrencode \ + mingw-w64-x86_64-libevent ``` -Build the Qt wallet: +Build: ```bash -qmake triangles-qt.pro -make -j$(nproc) +cmake -B build -G Ninja -DBUILD_QT=ON +cmake --build build ``` +### Build Options + +| Option | Default | Description | +|--------|---------|-------------| +| `BUILD_QT` | ON | Build the Qt GUI wallet | +| `BUILD_DAEMON` | ON | Build the headless daemon | +| `BUILD_TESTS` | OFF | Build unit tests | + ## Running ### First Run @@ -103,15 +123,13 @@ txindex=1 listen=1 server=1 daemon=1 -addnode=194.233.88.206 -addnode=74.208.167.19 -externalip= +proxy=127.0.0.1:9050 EOF trianglesd ``` -The node will connect to seed nodes and sync the blockchain automatically. +The node will connect to seed nodes over Tor and sync the blockchain automatically. ### Existing Wallet Holders @@ -155,7 +173,7 @@ Messages are encrypted end-to-end using AES and distributed through the peer net ### Tor Support -To connect through Tor, install the Tor daemon and add to your config: +Triangles is designed as a Tor-only network. Install the Tor daemon and configure your proxy: ``` # triangles.conf proxy=127.0.0.1:9050 @@ -199,7 +217,7 @@ Then set `externalip=` in `triangles.conf`. - **Block 9001+** - Proof-of-Stake only - **Block 17,651** - V5 hard fork (removed Tor v2, disabled checkpoint master key) - **December 8, 2022** - Chain frozen (all nodes offline) -- **March 11, 2026** - Chain revived with v5.0.0.0, staking resumed +- **March 11, 2026** - Chain revived, staking resumed ## Project Structure diff --git a/cmake/GenerateBuildInfoScript.cmake b/cmake/GenerateBuildInfoScript.cmake index 05ff58e..5f1e782 100644 --- a/cmake/GenerateBuildInfoScript.cmake +++ b/cmake/GenerateBuildInfoScript.cmake @@ -1,6 +1,7 @@ # cmake/GenerateBuildInfoScript.cmake # Called at build time by the custom target in GenerateBuildInfo.cmake. -# Replicates the logic of share/genbuild.sh. +# Reads the version from clientversion.h (single source of truth) and +# appends git commit info for non-release builds. # Read existing build.h first line if it exists set(OLD_LINE "") @@ -11,31 +12,69 @@ if(EXISTS "${OUTPUT_FILE}") endif() endif() -# Try exact tag match first (release builds) +# ── Read version from clientversion.h ── +file(STRINGS "${SOURCE_DIR}/src/clientversion.h" _ver_lines) +foreach(_line ${_ver_lines}) + if(_line MATCHES "^#define CLIENT_VERSION_MAJOR +([0-9]+)") + set(VER_MAJOR "${CMAKE_MATCH_1}") + elseif(_line MATCHES "^#define CLIENT_VERSION_MINOR +([0-9]+)") + set(VER_MINOR "${CMAKE_MATCH_1}") + elseif(_line MATCHES "^#define CLIENT_VERSION_REVISION +([0-9]+)") + set(VER_REVISION "${CMAKE_MATCH_1}") + elseif(_line MATCHES "^#define CLIENT_VERSION_BUILD +([0-9]+)") + set(VER_BUILD "${CMAKE_MATCH_1}") + endif() +endforeach() + +set(BASE_VERSION "v${VER_MAJOR}.${VER_MINOR}.${VER_REVISION}.${VER_BUILD}") + +# ── Get git commit info (suffix only, not the version number) ── +set(GIT_SUFFIX "") + +# Get short commit hash execute_process( - COMMAND git describe --tags --exact-match + COMMAND git rev-parse --short HEAD WORKING_DIRECTORY "${SOURCE_DIR}" - OUTPUT_VARIABLE GIT_DESC + OUTPUT_VARIABLE GIT_HASH OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET RESULT_VARIABLE _result ) -# Fall back to tag + commit distance -if(NOT _result EQUAL 0) +if(_result EQUAL 0 AND GIT_HASH) + # Check if working directory is dirty execute_process( - COMMAND git describe --tags --dirty + COMMAND git diff-index --quiet HEAD -- WORKING_DIRECTORY "${SOURCE_DIR}" - OUTPUT_VARIABLE GIT_DESC + RESULT_VARIABLE _dirty + ) + + # Check if HEAD is exactly on a tag matching our version + execute_process( + COMMAND git describe --tags --exact-match HEAD + WORKING_DIRECTORY "${SOURCE_DIR}" + OUTPUT_VARIABLE GIT_TAG OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET - RESULT_VARIABLE _result + RESULT_VARIABLE _tag_result ) - if(NOT _result EQUAL 0) - set(GIT_DESC "") + + set(_on_release_tag FALSE) + if(_tag_result EQUAL 0 AND GIT_TAG STREQUAL "${BASE_VERSION}") + set(_on_release_tag TRUE) + endif() + + # Only add git suffix for non-release builds (not on exact version tag, or dirty) + if(NOT _on_release_tag OR NOT _dirty EQUAL 0) + set(GIT_SUFFIX "-g${GIT_HASH}") + if(NOT _dirty EQUAL 0) + set(GIT_SUFFIX "${GIT_SUFFIX}-dirty") + endif() endif() endif() +set(FULL_VERSION "${BASE_VERSION}${GIT_SUFFIX}") + # Get commit timestamp execute_process( COMMAND git log -n 1 --format=%ci @@ -46,11 +85,7 @@ execute_process( ) # Build new content -if(GIT_DESC) - set(NEW_LINE "#define BUILD_DESC \"${GIT_DESC}\"") -else() - set(NEW_LINE "// No build information available") -endif() +set(NEW_LINE "#define BUILD_DESC \"${FULL_VERSION}\"") # Only write if changed if(NOT "${OLD_LINE}" STREQUAL "${NEW_LINE}") diff --git a/src/bootstrap.cpp b/src/bootstrap.cpp index b9dba99..a6bec06 100644 --- a/src/bootstrap.cpp +++ b/src/bootstrap.cpp @@ -3,7 +3,6 @@ #include "bootstrap.h" -#include #include #include #include @@ -12,6 +11,8 @@ #include "version.h" #include "uint256.h" +#include "netbase.h" +#include "net.h" #include #include @@ -19,12 +20,18 @@ #include #include +#ifdef WIN32 +#include +#else +#include +#include +#endif + // Forward declarations to avoid pulling in heavy consensus headers extern bool fTestNet; namespace Checkpoints { bool IsKnownCheckpoint(int nHeight, const uint256& hash); } namespace fs = boost::filesystem; -using boost::asio::ip::tcp; namespace Bootstrap { @@ -33,26 +40,48 @@ bool NeedsBootstrap(const fs::path& dataDir) return !fs::exists(dataDir / "blk0001.dat"); } +// Send all bytes on a raw socket +static bool SendAll(SOCKET sock, const char* data, size_t len) +{ + while (len > 0) { + int n = send(sock, data, (int)std::min(len, (size_t)65536), MSG_NOSIGNAL); + if (n <= 0) return false; + data += n; + len -= n; + } + return true; +} + +// Read until `delim` found in received data. Returns data including delimiter. +static bool RecvUntil(SOCKET sock, std::string& out, const std::string& delim) +{ + out.clear(); + char c; + while (true) { + int n = recv(sock, &c, 1, 0); + if (n <= 0) return false; + out += c; + if (out.size() >= delim.size() && + out.compare(out.size() - delim.size(), delim.size(), delim) == 0) + return true; + if (out.size() > 64 * 1024) return false; // header too large + } +} + bool DownloadFile(const std::string& host, const std::string& urlPath, const fs::path& destPath, ProgressCallback progressFn, std::string& strError) { try { - boost::asio::io_context io_context; - tcp::resolver resolver(io_context); - - boost::system::error_code resolve_ec; - tcp::resolver::results_type endpoints = - resolver.resolve(host, std::to_string(PORT), resolve_ec); - if (resolve_ec) { - strError = "Cannot resolve host: " + host; + // Connect through Tor SOCKS proxy (ConnectSocketByName respects SetProxy) + SOCKET hSocket = INVALID_SOCKET; + CService addr; + if (!ConnectSocketByName(addr, hSocket, host.c_str(), PORT, 30)) { + strError = "Cannot connect to " + host + " (check Tor proxy)"; return false; } - tcp::socket socket(io_context); - boost::asio::connect(socket, endpoints); - // Send HTTP GET request std::string request = "GET " + urlPath + " HTTP/1.1\r\n" @@ -60,85 +89,81 @@ bool DownloadFile(const std::string& host, const std::string& urlPath, "Connection: close\r\n" "User-Agent: Triangles\r\n" "\r\n"; - boost::asio::write(socket, boost::asio::buffer(request)); + + if (!SendAll(hSocket, request.data(), request.size())) { + closesocket(hSocket); + strError = "Failed to send request to " + host; + return false; + } // Read response headers - boost::asio::streambuf response_buf; - boost::asio::read_until(socket, response_buf, "\r\n\r\n"); + std::string headerData; + if (!RecvUntil(hSocket, headerData, "\r\n\r\n")) { + closesocket(hSocket); + strError = "Failed to read HTTP headers from " + host; + return false; + } - std::istream response_stream(&response_buf); - - // Parse status line - std::string http_version; + // Parse status code from "HTTP/1.x NNN ..." unsigned int status_code = 0; - response_stream >> http_version >> status_code; - std::string status_message; - std::getline(response_stream, status_message); + size_t sp = headerData.find(' '); + if (sp != std::string::npos) + status_code = atoi(headerData.c_str() + sp + 1); if (status_code != 200) { + closesocket(hSocket); strError = "HTTP error " + std::to_string(status_code) + " for " + urlPath; return false; } - // Parse headers for Content-Length + // Parse Content-Length int64_t content_length = 0; - std::string header_line; - while (std::getline(response_stream, header_line) && header_line != "\r") { - std::string lower_header = header_line; - std::transform(lower_header.begin(), lower_header.end(), - lower_header.begin(), ::tolower); - if (lower_header.find("content-length:") == 0) { - content_length = std::stoll(header_line.substr(header_line.find(':') + 1)); - } + std::string lowerHeaders = headerData; + std::transform(lowerHeaders.begin(), lowerHeaders.end(), + lowerHeaders.begin(), ::tolower); + size_t clPos = lowerHeaders.find("content-length:"); + if (clPos != std::string::npos) { + size_t valStart = clPos + 15; + size_t lineEnd = lowerHeaders.find("\r\n", valStart); + if (lineEnd != std::string::npos) + content_length = std::stoll(headerData.substr(valStart, lineEnd - valStart)); } // Open output file FILE* file = fopen(destPath.string().c_str(), "wb"); if (!file) { + closesocket(hSocket); strError = "Cannot create file: " + destPath.string(); return false; } + // Read body in chunks int64_t bytes_written = 0; - - // Write any data remaining in the header buffer (body starts here) - if (response_buf.size() > 0) { - std::istreambuf_iterator eos; - std::string remaining(std::istreambuf_iterator(response_stream), eos); - if (!remaining.empty()) { - fwrite(remaining.data(), 1, remaining.size(), file); - bytes_written += remaining.size(); - } - } - - // Read remaining body in chunks - std::vector chunk(65536); // 64 KB - boost::system::error_code ec; int64_t last_progress = 0; + char chunk[65536]; while (true) { - size_t n = socket.read_some(boost::asio::buffer(chunk), ec); - if (n > 0) { - fwrite(chunk.data(), 1, n, file); - bytes_written += n; - - // Report progress every 256 KB - if (progressFn && (bytes_written - last_progress >= 262144)) { - last_progress = bytes_written; - progressFn(bytes_written, content_length); - } - } - if (ec == boost::asio::error::eof) - break; - if (ec) { + int n = recv(hSocket, chunk, sizeof(chunk), 0); + if (n < 0) { fclose(file); + closesocket(hSocket); fs::remove(destPath); - strError = "Network error: " + ec.message(); + strError = "Network error during download"; return false; } + if (n == 0) break; // EOF + + fwrite(chunk, 1, n, file); + bytes_written += n; + + if (progressFn && (bytes_written - last_progress >= 262144)) { + last_progress = bytes_written; + progressFn(bytes_written, content_length); + } } fclose(file); + closesocket(hSocket); // Verify download size if Content-Length was provided if (content_length > 0 && bytes_written != content_length) { diff --git a/src/bootstrap.h b/src/bootstrap.h index 8a52f82..5573fd7 100644 --- a/src/bootstrap.h +++ b/src/bootstrap.h @@ -13,7 +13,6 @@ namespace Bootstrap { // Bootstrap server configuration static const char* DEFAULT_HOST = "bootstrap.cryptographic-triangles.org"; - static const char* FALLBACK_HOST = "194.233.88.206"; static const char* BASE_PATH = "/"; static const int PORT = 80; diff --git a/src/clientversion.h b/src/clientversion.h index 2bbd8fe..d91163c 100644 --- a/src/clientversion.h +++ b/src/clientversion.h @@ -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 7 -#define CLIENT_VERSION_REVISION 9 +#define CLIENT_VERSION_MINOR 8 +#define CLIENT_VERSION_REVISION 0 #define CLIENT_VERSION_BUILD 0 // Converts the parameter X to a string after macro replacement on X has been performed. diff --git a/src/init.cpp b/src/init.cpp index 1302f83..a19de55 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -921,11 +921,6 @@ bool AppInit2() }; bool success = Bootstrap::DownloadBootstrap(host, dataPath, progressFn, strError); - if (!success) { - host = Bootstrap::FALLBACK_HOST; - printf("\nBootstrap: primary host failed, trying fallback %s...\n", host.c_str()); - success = Bootstrap::DownloadBootstrap(host, dataPath, progressFn, strError); - } if (!success) { printf("\nBootstrap: failed: %s\n", strError.c_str()); diff --git a/src/main.cpp b/src/main.cpp index a159d71..be6a47a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2034,6 +2034,31 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck) // Track pending UTXOs so later txs in the same block can find inputs. if (fAssumeValid) { + // Track money supply from input/output values + int64_t nTxValueOut = tx.GetValueOut(); + nValueOut += nTxValueOut; + + if (!tx.IsCoinBase()) + { + int64_t nTxValueIn = 0; + for (const CTxIn& txin : tx.vin) + { + // Check in-block pending UTXOs first, then UTXO database + MapPrevTx::iterator it = mapPendingUtxos.find(txin.prevout); + if (it != mapPendingUtxos.end()) + nTxValueIn += it->second.nValue; + else + { + CUtxoEntry utxo; + if (txdb.ReadUtxo(txin.prevout.hash, txin.prevout.n, utxo)) + nTxValueIn += utxo.nValue; + } + } + nValueIn += nTxValueIn; + if (!tx.IsCoinStake()) + nFees += nTxValueIn - nTxValueOut; + } + // Add outputs to pending UTXOs for (unsigned int k = 0; k < tx.vout.size(); k++) { @@ -2774,21 +2799,22 @@ bool CBlock::AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos, const u // // Chain selection rules: // 1. Strictly greater trust always wins (normal case). - // 2. Equal trust with shallow fork (parent in main chain): use - // deterministic hash tiebreaker — lower tip hash wins. This - // resolves single-block PoS races where two stakers find valid - // blocks at the same height with identical difficulty. - // 3. Equal trust with deep fork (parent NOT in main chain): do NOT - // reorg. Without this rule, Tor-latency-induced multi-block - // forks cause nodes to oscillate between competing chains of - // similar trust, preventing convergence. + // 2. Equal trust: deterministic hash tiebreaker — lower tip hash wins. + // This ensures all nodes converge on the same chain even when two + // forks have identical cumulative difficulty (common in PoS). + // Rate-limited to one equal-trust reorg per 10 minutes to prevent + // oscillation from Tor-latency-induced competing announcements. bool fNewBest = false; + static int64_t nLastEqualTrustReorg = 0; if (pindexNew->nChainTrust > nBestChainTrust) fNewBest = true; else if (pindexNew->nChainTrust == nBestChainTrust && pindexBest && - pindexNew->pprev && pindexNew->pprev->IsInMainChain() && - pindexNew->GetBlockHash() < pindexBest->GetBlockHash()) + pindexNew->GetBlockHash() < pindexBest->GetBlockHash() && + GetTime() - nLastEqualTrustReorg > 10 * 60) + { fNewBest = true; + nLastEqualTrustReorg = GetTime(); + } if (fNewBest) { @@ -3735,10 +3761,6 @@ bool FastImportBlockFile() } pindexNew->nStakeModifierChecksum = GetStakeModifierChecksum(pindexNew); - // Money supply tracking - pindexNew->nMint = 0; - pindexNew->nMoneySupply = (pindexNew->pprev ? pindexNew->pprev->nMoneySupply : 0); - // PoS stake seen set if (pindexNew->IsProofOfStake()) setStakeSeen.insert(make_pair(pindexNew->prevoutStake, pindexNew->nStakeTime)); @@ -3751,10 +3773,10 @@ bool FastImportBlockFile() if (pindexNew->pprev) pindexNew->pprev->pnext = pindexNew; - // Write block index to batch - txdb.WriteBlockIndex(CDiskBlockIndex(pindexNew)); - - // Build tx index + UTXO entries + // Build tx index + UTXO entries, tracking money supply + int64_t nBlockValueIn = 0; + int64_t nBlockValueOut = 0; + int64_t nFees = 0; unsigned int nTxPos = nBlockPos + ::GetSerializeSize(CBlock(), SER_DISK, CLIENT_VERSION) - (2 * GetSizeOfCompactSize(0)) + GetSizeOfCompactSize(block.vtx.size()); for (unsigned int i = 0; i < block.vtx.size(); i++) @@ -3765,11 +3787,23 @@ bool FastImportBlockFile() txdb.UpdateTxIndex(hashTx, CTxIndex(posThisTx, tx.vout.size())); nTxPos += ::GetSerializeSize(tx, SER_DISK, CLIENT_VERSION); - // UTXO entries + int64_t nTxValueOut = tx.GetValueOut(); + nBlockValueOut += nTxValueOut; + + // UTXO entries — read input values before erasing for money supply if (!tx.IsCoinBase()) { + int64_t nTxValueIn = 0; for (const CTxIn& txin : tx.vin) + { + CUtxoEntry utxo; + if (txdb.ReadUtxo(txin.prevout.hash, txin.prevout.n, utxo)) + nTxValueIn += utxo.nValue; txdb.EraseUtxo(txin.prevout.hash, txin.prevout.n); + } + nBlockValueIn += nTxValueIn; + if (!tx.IsCoinStake()) + nFees += nTxValueIn - nTxValueOut; } for (unsigned int k = 0; k < tx.vout.size(); k++) { @@ -3787,6 +3821,13 @@ bool FastImportBlockFile() } } + // Money supply tracking — matches ConnectBlock formula + pindexNew->nMint = nBlockValueOut - nBlockValueIn + nFees; + pindexNew->nMoneySupply = (pindexNew->pprev ? pindexNew->pprev->nMoneySupply : 0) + nBlockValueOut - nBlockValueIn; + + // Write block index to batch + txdb.WriteBlockIndex(CDiskBlockIndex(pindexNew)); + // Update best chain if (pindexNew->nChainTrust > nBestChainTrust) { diff --git a/src/net.cpp b/src/net.cpp index ccca9b9..f827510 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -1403,7 +1403,32 @@ void ThreadOnionSeed(void* parg) // This is the primary discovery mechanism — seeds.cryptographic-triangles.org ThreadHTTPSeedFetch2(NULL); - printf("ThreadOnionSeed: seeding complete\n"); + printf("ThreadOnionSeed: initial seeding complete\n"); + + // Periodic re-seeding: if the node becomes isolated (0 outbound peers), + // re-fetch the seed list. Check every 10 minutes, re-seed at most once + // per 30 minutes to avoid hammering the seed server. + int64_t nLastReseed = GetTime(); + while (!fShutdown) { + for (int i = 0; i < 600 && !fShutdown; i++) // sleep 10 minutes + MilliSleep(1000); + + if (fShutdown) break; + + int nOutbound = 0; + { + LOCK(cs_vNodes); + for (CNode* pnode : vNodes) + if (!pnode->fInbound) + nOutbound++; + } + + if (nOutbound == 0 && GetTime() - nLastReseed > 30 * 60) { + printf("ThreadOnionSeed: no outbound peers, re-seeding...\n"); + ThreadHTTPSeedFetch2(NULL); + nLastReseed = GetTime(); + } + } } @@ -1622,12 +1647,8 @@ void ThreadHTTPSeedFetch2(void* parg) 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); - } + // Tor-native: skip non-.onion addresses + continue; } if (port <= 0 || port > 65535) diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index c7fdeed..18dbbb2 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -389,7 +389,7 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(const QListfirst) < 1) { printf("CreateStealthTransaction(): Error creating narration key."); continue; diff --git a/src/rpcwallet.cpp b/src/rpcwallet.cpp index 351bfc6..7a792e7 100644 --- a/src/rpcwallet.cpp +++ b/src/rpcwallet.cpp @@ -1944,7 +1944,8 @@ Value clearwallettransactions(const Array& params, bool fHelp) if (datKey.get_data() == NULL || datValue.get_data() == NULL || ret != 0) { - snprintf(cbuf, sizeof(cbuf), "wallet DB error %d, %s", ret, db_strerror(ret)); + const char* dbErr = db_strerror(ret); + snprintf(cbuf, sizeof(cbuf), "wallet DB error %d, %s", ret, dbErr ? dbErr : "unknown"); throw runtime_error(cbuf); }; diff --git a/src/txdb-leveldb.cpp b/src/txdb-leveldb.cpp index b256d3f..e99dde4 100644 --- a/src/txdb-leveldb.cpp +++ b/src/txdb-leveldb.cpp @@ -613,6 +613,50 @@ bool CTxDB::LoadBlockIndex() hashBestChain.ToString().substr(0,20).c_str(), nBestHeight, CBigNum(nBestChainTrust).ToString().c_str(), DateTimeStrFormat("%x %H:%M:%S", pindexBest->GetBlockTime()).c_str()); + // Re-evaluate best chain: scan for competing tips with equal or greater trust. + // This fixes nodes stuck on the wrong fork after consensus rule changes. + { + CBlockIndex* pindexBetter = NULL; + for (const auto& item : mapBlockIndex) + { + CBlockIndex* pindex = item.second; + if (pindex == pindexBest) + continue; + if (pindex->nChainTrust > nBestChainTrust) + { + pindexBetter = pindex; + break; + } + if (pindex->nChainTrust == nBestChainTrust && + pindex->GetBlockHash() < pindexBest->GetBlockHash()) + { + if (!pindexBetter || pindex->GetBlockHash() < pindexBetter->GetBlockHash()) + pindexBetter = pindex; + } + } + if (pindexBetter) + { + printf("LoadBlockIndex(): found better chain tip %s at height %d (trust %s vs %s)\n", + pindexBetter->GetBlockHash().ToString().substr(0,20).c_str(), + pindexBetter->nHeight, + CBigNum(pindexBetter->nChainTrust).ToString().c_str(), + CBigNum(nBestChainTrust).ToString().c_str()); + CBlock block; + if (block.ReadFromDisk(pindexBetter)) + { + CTxDB txdb2; + if (block.SetBestChain(txdb2, pindexBetter)) + { + hashBestChain = pindexBetter->GetBlockHash(); + pindexBest = pindexBetter; + nBestHeight = pindexBetter->nHeight; + nBestChainTrust = pindexBetter->nChainTrust; + printf("LoadBlockIndex(): switched to better chain tip\n"); + } + } + } + } + // triangles: load hashSyncCheckpoint (best-effort, non-fatal) if (!ReadSyncCheckpoint(Checkpoints::hashSyncCheckpoint)) printf("LoadBlockIndex(): no sync checkpoint in DB, using default\n"); diff --git a/src/version.cpp b/src/version.cpp index 577412b..a5cd105 100644 --- a/src/version.cpp +++ b/src/version.cpp @@ -48,9 +48,9 @@ const std::string CLIENT_NAME("Cryptographic Triangles"); #ifndef BUILD_DESC # ifdef GIT_COMMIT_ID -# define BUILD_DESC BUILD_DESC_FROM_COMMIT(DISPLAY_VERSION_MAJOR, DISPLAY_VERSION_MINOR, DISPLAY_VERSION_REVISION, DISPLAY_VERSION_BUILD, GIT_COMMIT_ID) +# define BUILD_DESC BUILD_DESC_FROM_COMMIT(CLIENT_VERSION_MAJOR, CLIENT_VERSION_MINOR, CLIENT_VERSION_REVISION, CLIENT_VERSION_BUILD, GIT_COMMIT_ID) # else -# define BUILD_DESC BUILD_DESC_FROM_UNKNOWN(DISPLAY_VERSION_MAJOR, DISPLAY_VERSION_MINOR, DISPLAY_VERSION_REVISION, DISPLAY_VERSION_BUILD) +# define BUILD_DESC BUILD_DESC_FROM_UNKNOWN(CLIENT_VERSION_MAJOR, CLIENT_VERSION_MINOR, CLIENT_VERSION_REVISION, CLIENT_VERSION_BUILD) # endif #endif diff --git a/src/version.h b/src/version.h index ecf3c30..8804a8f 100644 --- a/src/version.h +++ b/src/version.h @@ -51,9 +51,4 @@ static const int BIP0031_VERSION = 60000; // "mempool" command, enhanced "getdata" behavior starts with this version: static const int MEMPOOL_GD_VERSION = 60002; -#define DISPLAY_VERSION_MAJOR 5 -#define DISPLAY_VERSION_MINOR 7 -#define DISPLAY_VERSION_REVISION 6 -#define DISPLAY_VERSION_BUILD 0 - #endif