Compare commits

...

11 Commits

Author SHA1 Message Date
sami7777 65b9417c28 Eliminate all blocking LOCK(cs_wallet) calls from UI thread
Build All Platforms / build-windows-qt (push) Waiting to run
Build All Platforms / build-windows-daemon (push) Waiting to run
Build All Platforms / build-linux-qt (push) Waiting to run
Build All Platforms / build-linux-daemon (push) Waiting to run
Build All Platforms / build-macos (push) Waiting to run
Build All Platforms / release (push) Blocked by required conditions
During sync, NotifyTransactionChanged fires for every wallet tx in
every block, each triggering 3 blocking LOCK(cs_wallet) calls on
the UI thread: updateWallet, GetAllBalances, getNumTransactions.
With the block processing thread holding cs_wallet almost continuously,
the UI thread blocks waiting for the lock - causing "not responding".

Fixes:
- GetAllBalances: LOCK → TRY_LOCK, returns false if busy
- updateWallet (tx table): LOCK → TRY_LOCK, skips if busy
- updateTransaction: removed checkBalanceChanged() call entirely
  (pollBalanceChanged timer handles it every 2.5s with TRY_LOCK)
- getNumTransactions: replaced with rowCount() from cached model

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 19:32:04 -07:00
sami7777 ed87543153 Fix Linux Qt build: int64_t/qint64 type mismatch
On Linux, int64_t is long but qint64 is long long - different types
that can't bind to the same reference. Use int64_t locals to match
the GetAllBalances signature.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 18:05:43 -07:00
sami7777 6e9dbb1aa9 Bump version to 5.3.5 - fix out-of-sync display for PoS chains
Remove time-based sync check that showed "out of sync" when blocks
were >6 hours old. For PoS chains with few stakers, blocks can be
hours apart - that's idle, not out of sync. Now uses block count
only. Also adds periodic UI refresh every 30s and switches cached
stake weight from volatile to std::atomic.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 17:59:18 -07:00
SamiAhmed7777 a6ec711cfa Merge pull request #1 from SamiAhmed7777/cleanup/desloppify
Code cleanup: Documentation and C++11 compliance fixes
2026-03-22 15:43:35 -07:00
Krystie 6877aeaddb chore: Update .gitignore for build artifacts 2026-03-22 22:54:21 +01:00
Krystie 60067e1a88 fix: Add space between string literals and PRId64 macros
Fixes C++11 literal-suffix warnings in util.h, net.h, and alert.cpp.
Required space between string literal and macro per C++11 standard.

No functional changes - formatting only.
2026-03-22 22:46:48 +01:00
Krystie e91ccd8786 docs: Document critical TODOs/FIXMEs with context
- Add CLEANUP_NOTES.md documenting cleanup strategy
- Add TODO_DOCUMENTATION.md with detailed context for all TODOs
- Improve inline comments for thread safety issue in rpcmining.cpp
- Clarify potential collision note in walletmodel.cpp
- Remove unclear 'DRM' comment, replace with descriptive text

No functional changes - documentation only.
2026-03-22 22:26:52 +01:00
sami7777 96fb7d5040 Bump version to 5.3.4 - fix UI freezing during staking
Build All Platforms / build-windows-qt (push) Waiting to run
Build All Platforms / build-windows-daemon (push) Waiting to run
Build All Platforms / build-linux-qt (push) Waiting to run
Build All Platforms / build-linux-daemon (push) Waiting to run
Build All Platforms / build-macos (push) Waiting to run
Build All Platforms / release (push) Blocked by required conditions
Move GetStakeWeight() off the UI thread by caching in the staking
miner thread. Replace blocking LOCK(cs_vNodes) with TRY_LOCK in
clientmodel and staking icon updates. Fix out-of-sync label getting
stuck when disconnected. Add daemon bootstrap and faster IBD pipeline.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 21:28:11 -07:00
sami7777 47cf8abbda Add daemon bootstrap, repeating bootstrap prompt, faster IBD pipeline
- Daemon: add -bootstrap flag to download chain files from server on startup
- Qt: bootstrap prompt shows every launch with "Don't show this again" checkbox
- IBD: reduce pipeline refill interval from 1000 to 100 blocks for faster sync
- Add bootstrap.o to daemon makefiles (mingw + unix)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-19 21:35:12 -07:00
sami7777 2abd494fec Bump version to 5.3.3 and update CI version
Build All Platforms / build-windows-qt (push) Waiting to run
Build All Platforms / build-windows-daemon (push) Waiting to run
Build All Platforms / build-linux-qt (push) Waiting to run
Build All Platforms / build-linux-daemon (push) Waiting to run
Build All Platforms / build-macos (push) Waiting to run
Build All Platforms / release (push) Blocked by required conditions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-19 18:54:44 -07:00
sami7777 378b0370e3 Improve peer connectivity, UI responsiveness, and add auto-bootstrap
Peer connectivity (small network optimizations):
- Reduce hardcoded seed fallback delay from 30s to 10s
- Reduce peer retry interval from 600s to 120s
- Lower staking minimum peers from 3 to 1
- Relay addr messages to all connected peers instead of just 2

UI responsiveness:
- Add progress reporting to ScanForWalletTransactions (every 10K blocks)
- Use TRY_LOCK in WalletModel::pollBalanceChanged to avoid blocking UI
- Use TRY_LOCK in TransactionTablePriv::refreshWallet with retry

Auto-bootstrap:
- Add bootstrap.h/cpp with HTTP download via boost::asio
- On first run, prompt user to download blockchain snapshot from
  bootstrap.cryptographic-triangles.org directly into data directory
- Downloads filelist.txt manifest then each file with progress dialog
- Falls back to IP 194.233.88.206 if DNS fails
- Gracefully continues to P2P sync if bootstrap unavailable

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-19 18:02:47 -07:00
31 changed files with 1203 additions and 69 deletions
+1 -1
View File
@@ -9,7 +9,7 @@ on:
workflow_dispatch:
env:
VERSION: "5.2.0"
VERSION: "5.3.5"
jobs:
build-windows-qt:
+3
View File
@@ -52,3 +52,6 @@ triangles.conf
*.key
*.cert
*.gpg
*.o
src/trianglesd
src/obj/
+94
View File
@@ -0,0 +1,94 @@
# Triangles Codebase Cleanup Notes
## Overview
Systematic code quality improvements for the Triangles cryptocurrency codebase (v5.3.4+).
**Goal:** Improve maintainability without changing behavior or breaking consensus.
## Inventory
### TODOs/FIXMEs Found (38 total)
#### High Priority (Affects Safety/Correctness)
- `rpcmining.cpp:263` - **Thread safety issue** in mapNewBlock (static variable, no mutex)
- `walletmodel.cpp:249` - **Potential collision** in balance calculation
- `smessage.cpp:863, 2219, 2373` - **File size limit** (files must be split if >2GB)
#### Medium Priority (Encapsulation/Security)
- `protocol.h:50, 100, 132` - Public members should be private (3 locations)
- `wallet.h:378` - nOrderPos calculation should move elsewhere
- `wallet.cpp:733, 1732` - Change output handling needs improvement
- `rpcwallet.cpp:1474, 1513, 1569` - SecureString operator= missing (forced .c_str())
#### Low Priority (Nice-to-Have)
- `util.cpp:1322` - Disabled feature needs verification
- `tor/tor_embedded.cpp:209` - Tor 0.4.9+ shutdown API upgrade
- `init.cpp:442` - Remaining sanity checks (see Bitcoin issue #4081)
- `rpcmining.cpp:232` - DRM comment (unclear what it means)
- `smessage.cpp:*` - Various improvements (hash inclusion, thread safety, defaults)
- `qt/*` - UI improvements (decrypt not supported, message filtering, OSX startup)
#### External/Third-Party (Don't Touch)
- `leveldb/*` - LevelDB library TODOs (upstream issues)
## Code Quality Issues
### Using namespace std (37 files)
All in .cpp files - **this is fine for .cpp**, problematic only in headers.
No headers have this issue, so **no action needed**.
### Printf/Cout Usage (56 files)
Most cryptocurrency code uses printf for early init/error handling before logging is available.
**Review needed:** Check if these are legitimate early-init cases or should use LogPrintf.
## Cleanup Plan (Safest → Riskiest)
### Phase 1: Documentation & Comments ✅ SAFE
1. Document all TODOs with context (why deferred, what's needed)
2. Add function-level comments for complex logic
3. Improve inline comments for clarity
### Phase 2: Low-Risk Code Quality 🟨 MEDIUM RISK
4. Fix compiler warnings (-Wall -Wextra)
5. Add const correctness where missing
6. Remove commented-out dead code
7. Standardize code formatting (if inconsistent)
### Phase 3: Functional Improvements 🟥 HIGH RISK (Skip for now)
8. Fix thread safety issue in rpcmining.cpp (requires testing)
9. Improve protocol.h encapsulation (may affect other code)
10. Address >2GB file handling in smessage.cpp
## Decisions
### What NOT to Change
- **Consensus code** - main.cpp (validation), kernel.cpp (PoS), miner.cpp (staking)
- **Serialization** - Any READWRITE, serialize/deserialize code
- **Protocol constants** - Network message types, version numbers
- **Third-party code** - leveldb/, tor/, sph_types.h, xxhash/, lz4/
### What's Safe to Change
- Comments and documentation
- Variable names (in non-consensus code)
- Code organization (splitting large functions)
- Logging statements
- UI code (qt/)
- RPC interface (as long as API contract preserved)
## Initial Cleanup (2026-03-22)
### Actions Taken
1. Created this documentation file
2. Created cleanup/desloppify branch
3. Inventoried all TODOs/FIXMEs
### Next Steps
1. Add documentation comments to TODO items
2. Review printf/cout usage patterns
3. Check for compiler warnings
4. Consider low-risk improvements
## Notes
- This is a Bitcoin-derived codebase, so many patterns follow Bitcoin Core conventions
- Recent v5.3.x work already modernized to C++17 and removed Boost - good foundation
- Code is generally well-structured; main improvements are documentation and minor cleanup
+3 -3
View File
@@ -3,7 +3,7 @@
# Generated by qmake (3.1) (Qt 5.15.18)
# Project: triangles-qt.pro
# Template: app
# Command: C:/msys64/mingw64/bin/qmake-qt5.exe -o Makefile triangles-qt.pro USE_QRCODE=1 USE_UPNP=-
# Command: C:/msys64/mingw64/bin/qmake-qt5.exe -o Makefile triangles-qt.pro -spec win32-g++ CONFIG+=release
#############################################################################
MAKEFILE = Makefile
@@ -156,7 +156,7 @@ Makefile: triangles-qt.pro C:/msys64/mingw64/share/qt5/mkspecs/win32-g++/qmake.c
C:/msys64/mingw64/lib/qtmain.prl \
C:/msys64/mingw64/share/qt5/mkspecs/features/build_pass.prf \
src/qt/triangles.qrc
$(QMAKE) -o Makefile triangles-qt.pro USE_QRCODE=1 USE_UPNP=-
$(QMAKE) -o Makefile triangles-qt.pro -spec win32-g++ CONFIG+=release
C:/msys64/mingw64/share/qt5/mkspecs/features/spec_pre.prf:
C:/msys64/mingw64/share/qt5/mkspecs/qdevice.pri:
C:/msys64/mingw64/share/qt5/mkspecs/features/device_config.prf:
@@ -244,7 +244,7 @@ C:/msys64/mingw64/lib/qtmain.prl:
C:/msys64/mingw64/share/qt5/mkspecs/features/build_pass.prf:
src/qt/triangles.qrc:
qmake: FORCE
@$(QMAKE) -o Makefile triangles-qt.pro USE_QRCODE=1 USE_UPNP=-
@$(QMAKE) -o Makefile triangles-qt.pro -spec win32-g++ CONFIG+=release
qmake_all: FORCE
+124
View File
@@ -0,0 +1,124 @@
# TODO/FIXME Documentation
Detailed context for each TODO/FIXME in the codebase.
## Critical (Needs Attention)
### src/rpcmining.cpp:263 - Thread Safety Issue
```cpp
static mapNewBlock_t mapNewBlock; // FIXME: thread safety
```
**Issue:** Static variable accessed by multiple RPC threads without mutex protection.
**Impact:** Potential race condition in getwork RPC (used for mining).
**Status:** Low priority - PoW mining ended at block 9000, this code path rarely used.
**Fix:** Add std::mutex and lock_guard if getwork usage increases.
### src/qt/walletmodel.cpp:249 - Collision Risk
```cpp
if((total + nFeeRequired) > nBalance) // FIXME: could cause collisions in the future
```
**Issue:** Balance check may have edge case causing transaction collisions.
**Context:** In createTransaction fee calculation loop.
**Status:** Needs investigation - unclear what "collisions" means here.
**Fix:** Review Bitcoin Core's current implementation of this logic.
### src/smessage.cpp - File Size Limits
```cpp
// Lines 863, 2219, 2373: "TODO files must be split if > 2GB"
```
**Issue:** Secure message storage files not split when exceeding 2GB.
**Impact:** May fail on 32-bit systems or with large message volumes.
**Status:** Low priority - unlikely to reach 2GB in practice.
**Fix:** Implement file rotation when approaching 2GB limit.
## Medium Priority (Encapsulation/API)
### src/protocol.h - Make Members Private
```cpp
// Lines 50, 100, 132: "TODO: make private (improves encapsulation)"
```
**Issue:** CAddress, CInv, CMessageHeader have public data members.
**Impact:** Poor encapsulation, harder to maintain invariants.
**Status:** Deferred - would require extensive refactoring.
**Fix:** Add getter/setter methods, make members private, update all call sites.
### src/wallet.h:378 - nOrderPos Calculation
```cpp
nOrderPos = -1; // TODO: calculate elsewhere
```
**Issue:** Transaction ordering position calculated in constructor.
**Impact:** Minor - works but not ideal separation of concerns.
**Status:** Deferred - no functional issue.
**Fix:** Move calculation to WalletDB when transaction is added.
### src/rpcwallet.cpp - SecureString Operator
```cpp
// Lines 1474, 1513, 1569: "TODO: get rid of this .c_str()"
```
**Issue:** SecureString missing operator=(std::string).
**Impact:** Forced to use .c_str() which exposes password temporarily.
**Status:** Deferred - would require SecureString class modification.
**Fix:** Add `SecureString& operator=(const std::string&)` method.
## Low Priority (Nice-to-Have)
### src/util.cpp:1322 - Disabled Feature
```cpp
// TODO: This is currently disabled because it needs to be verified to work
```
**Context:** File descriptor management code.
**Status:** Intentionally disabled pending verification.
**Fix:** Test thoroughly, then enable if needed.
### src/tor/tor_embedded.cpp:209 - Tor Shutdown API
```cpp
// TODO: Tor 0.4.9+ may add tor_api_shutdown(), use it when available
```
**Context:** Embedded Tor cleanup.
**Status:** Waiting for upstream Tor API.
**Fix:** Check Tor 0.4.9+ releases for new API, integrate when stable.
### src/init.cpp:442 - Sanity Checks
```cpp
// TODO: remaining sanity checks, see #4081
```
**Context:** Bitcoin Core issue #4081 - additional startup sanity checks.
**Status:** Deferred - core checks already in place.
**Fix:** Review Bitcoin Core's current sanity check implementation.
### src/rpcmining.cpp:232 - DRM Comment
```cpp
CDataStream(coinbase, SER_NETWORK, PROTOCOL_VERSION) >> pblock->vtx[0]; // FIXME - DRM!
```
**Issue:** Unclear what "DRM" means here - likely "Data Race Maybe"?
**Status:** Needs clarification from original author.
**Fix:** Investigate if there's an actual issue, otherwise remove comment.
## Deferred (External/Low Impact)
### LevelDB TODOs (src/leveldb/*)
**Status:** Upstream LevelDB issues - don't modify embedded library.
**Action:** None - track upstream LevelDB project.
### Qt TODOs (src/qt/*)
**Status:** UI improvements, not critical.
**Action:** Track as nice-to-have enhancements.
### Secure Message TODOs (src/smessage.cpp)
Multiple minor improvements suggested:
- Include hash in certain operations
- Improve thread shutdown
- Set default recv/recvAnon behavior
- Update outbox after PoW completes
**Status:** Non-critical enhancements.
**Action:** Consider for future encrypted messaging upgrades.
## Summary
**Critical:** 3 items (thread safety, balance collision, file limits)
**Medium:** 6 items (encapsulation, SecureString)
**Low:** 5 items (disabled features, upstream APIs)
**Deferred:** ~24 items (external libs, minor enhancements)
**Recommendation:** Focus on documenting critical items in code comments, defer fixes until specific issues arise.
+2 -2
View File
@@ -53,8 +53,8 @@ std::string CUnsignedAlert::ToString() const
return strprintf(
"CAlert(\n"
" nVersion = %d\n"
" nRelayUntil = %"PRId64"\n"
" nExpiration = %"PRId64"\n"
" nRelayUntil = %" PRId64 "\n"
" nExpiration = %" PRId64 "\n"
" nID = %d\n"
" nCancel = %d\n"
" setCancel = %s\n"
+378
View File
@@ -0,0 +1,378 @@
// Copyright (c) 2024 Triangles developers
// Distributed under the MIT/X11 software license
#include "bootstrap.h"
#include <boost/asio.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/algorithm/string.hpp>
#include <zlib.h>
#include <fstream>
#include <sstream>
#include <cstdio>
#include <cstring>
namespace fs = boost::filesystem;
using boost::asio::ip::tcp;
namespace Bootstrap {
bool NeedsBootstrap(const fs::path& dataDir)
{
return !fs::exists(dataDir / "blk0001.dat");
}
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;
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"
"Host: " + host + "\r\n"
"Connection: close\r\n"
"User-Agent: Triangles\r\n"
"\r\n";
boost::asio::write(socket, boost::asio::buffer(request));
// Read response headers
boost::asio::streambuf response_buf;
boost::asio::read_until(socket, response_buf, "\r\n\r\n");
std::istream response_stream(&response_buf);
// Parse status line
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) {
strError = "HTTP error " + std::to_string(status_code) + " for " + urlPath;
return false;
}
// Parse headers for 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));
}
}
// Open output file
FILE* file = fopen(destPath.string().c_str(), "wb");
if (!file) {
strError = "Cannot create file: " + destPath.string();
return false;
}
int64_t bytes_written = 0;
// Write any data remaining in the header buffer (body starts here)
if (response_buf.size() > 0) {
std::istreambuf_iterator<char> eos;
std::string remaining(std::istreambuf_iterator<char>(response_stream), eos);
if (!remaining.empty()) {
fwrite(remaining.data(), 1, remaining.size(), file);
bytes_written += remaining.size();
}
}
// Read remaining body in chunks
std::vector<char> chunk(65536); // 64 KB
boost::system::error_code ec;
int64_t last_progress = 0;
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) {
fclose(file);
fs::remove(destPath);
strError = "Network error: " + ec.message();
return false;
}
}
fclose(file);
// Verify download size if Content-Length was provided
if (content_length > 0 && bytes_written != content_length) {
fs::remove(destPath);
strError = "Incomplete download: got " + std::to_string(bytes_written)
+ " of " + std::to_string(content_length) + " bytes";
return false;
}
return true;
} catch (std::exception& e) {
strError = std::string("Download failed: ") + e.what();
return false;
}
}
bool FetchFileList(const std::string& host,
std::vector<std::string>& files,
std::string& strError)
{
// Download filelist.txt to a temp file
fs::path tmpPath = fs::temp_directory_path() / "triangles_bootstrap_filelist.txt";
std::string urlPath = std::string(BASE_PATH) + "filelist.txt";
if (!DownloadFile(host, urlPath, tmpPath, nullptr, strError))
return false;
// Read lines
std::ifstream in(tmpPath.string().c_str());
if (!in.is_open()) {
strError = "Cannot read downloaded file list";
return false;
}
files.clear();
std::string line;
while (std::getline(in, line)) {
boost::trim(line);
if (!line.empty() && line[0] != '#')
files.push_back(line);
}
in.close();
fs::remove(tmpPath);
if (files.empty()) {
strError = "File list is empty";
return false;
}
return true;
}
// --- tar.gz bootstrap support ---
namespace {
// Parse a tar octal field (ASCII octal, null/space terminated)
static int64_t ParseTarOctal(const char* field, size_t len)
{
int64_t result = 0;
for (size_t i = 0; i < len && field[i] != '\0' && field[i] != ' '; i++) {
if (field[i] < '0' || field[i] > '7') continue;
result = (result << 3) | (field[i] - '0');
}
return result;
}
// Extract a tar.gz file to a destination directory
static bool ExtractTarGz(const fs::path& tarGzPath,
const fs::path& destDir,
std::string& strError)
{
gzFile gz = gzopen(tarGzPath.string().c_str(), "rb");
if (!gz) {
strError = "Cannot open " + tarGzPath.string();
return false;
}
gzbuffer(gz, 262144); // 256 KB buffer for performance
char header[512];
while (true) {
int bytesRead = gzread(gz, header, 512);
if (bytesRead == 0) break; // EOF
if (bytesRead != 512) {
strError = "Truncated tar header";
gzclose(gz);
return false;
}
// End-of-archive marker (zero block)
bool allZero = true;
for (int i = 0; i < 512; i++) {
if (header[i] != 0) { allZero = false; break; }
}
if (allZero) break;
// Parse filename: name (offset 0, 100 bytes) + optional prefix (offset 345, 155 bytes)
char name[101] = {0};
char prefix[156] = {0};
memcpy(name, header, 100);
memcpy(prefix, header + 345, 155);
std::string fullName;
if (prefix[0] != '\0')
fullName = std::string(prefix) + "/" + std::string(name);
else
fullName = std::string(name);
// Security: reject absolute paths and path traversal
if (fullName.empty() || fullName[0] == '/' || fullName.find("..") != std::string::npos) {
strError = "Unsafe path in tar archive: " + fullName;
gzclose(gz);
return false;
}
char typeflag = header[156];
int64_t fileSize = ParseTarOctal(header + 124, 12);
if (typeflag == '5' || (!fullName.empty() && fullName.back() == '/')) {
// Directory entry
fs::create_directories(destDir / fullName);
} else if (typeflag == '0' || typeflag == '\0') {
// Regular file
fs::path filePath = destDir / fullName;
fs::create_directories(filePath.parent_path());
FILE* outFile = fopen(filePath.string().c_str(), "wb");
if (!outFile) {
strError = "Cannot create file: " + filePath.string();
gzclose(gz);
return false;
}
int64_t remaining = fileSize;
char buf[65536];
while (remaining > 0) {
int toRead = (remaining > (int64_t)sizeof(buf)) ? (int)sizeof(buf) : (int)remaining;
int n = gzread(gz, buf, toRead);
if (n <= 0) {
fclose(outFile);
strError = "Truncated tar data for: " + fullName;
gzclose(gz);
return false;
}
fwrite(buf, 1, n, outFile);
remaining -= n;
}
fclose(outFile);
// Skip padding to next 512-byte boundary
int64_t pad = (512 - (fileSize % 512)) % 512;
if (pad > 0) {
char padBuf[512];
if (gzread(gz, padBuf, (unsigned)pad) != (int)pad) {
strError = "Truncated tar padding for: " + fullName;
gzclose(gz);
return false;
}
}
} else {
// Unknown entry type - skip its data
int64_t totalSkip = fileSize + ((512 - (fileSize % 512)) % 512);
char skipBuf[512];
while (totalSkip > 0) {
int toRead = (totalSkip > 512) ? 512 : (int)totalSkip;
if (gzread(gz, skipBuf, toRead) != toRead) break;
totalSkip -= toRead;
}
}
}
gzclose(gz);
return true;
}
} // anonymous namespace
bool DownloadBootstrap(const std::string& host,
const fs::path& dataDir,
ProgressCallback progressFn,
std::string& strError)
{
bool gotBlockFile = false;
// Try downloading bootstrap.tar.gz first
fs::path tmpTarGz = dataDir / "bootstrap.tar.gz.tmp";
std::string tarUrl = std::string(BASE_PATH) + "bootstrap.tar.gz";
bool tarDownloaded = DownloadFile(host, tarUrl, tmpTarGz, progressFn, strError);
if (tarDownloaded) {
bool extractOk = ExtractTarGz(tmpTarGz, dataDir, strError);
fs::remove(tmpTarGz);
if (extractOk && fs::exists(dataDir / "blk0001.dat"))
gotBlockFile = true;
// If extraction failed, fall through to legacy path
}
if (!gotBlockFile) {
// Fallback: try filelist.txt + individual file downloads
std::string fallbackError;
std::vector<std::string> files;
if (!FetchFileList(host, files, fallbackError)) {
if (!tarDownloaded)
strError = strError + " (fallback also failed: " + fallbackError + ")";
else
strError = "Extraction failed: " + strError + " (fallback also failed: " + fallbackError + ")";
return false;
}
for (size_t i = 0; i < files.size(); i++) {
fs::path destPath = dataDir / files[i];
fs::create_directories(destPath.parent_path());
std::string urlPath = std::string(BASE_PATH) + files[i];
if (!DownloadFile(host, urlPath, destPath, progressFn, strError))
return false;
}
gotBlockFile = fs::exists(dataDir / "blk0001.dat");
}
if (!gotBlockFile) {
strError = "No blk0001.dat after download";
return false;
}
// Remove any extracted txleveldb/ and database/ - they were built on
// a different machine and won't work here. FastImportBlockFile() will
// rebuild the index directly from blk0001.dat on next startup.
fs::path txleveldb = dataDir / "txleveldb";
fs::path database = dataDir / "database";
if (fs::exists(txleveldb))
fs::remove_all(txleveldb);
if (fs::exists(database))
fs::remove_all(database);
return true;
}
} // namespace Bootstrap
+46
View File
@@ -0,0 +1,46 @@
// Copyright (c) 2024 Triangles developers
// Distributed under the MIT/X11 software license
#ifndef TRIANGLES_BOOTSTRAP_H
#define TRIANGLES_BOOTSTRAP_H
#include <string>
#include <vector>
#include <functional>
#include <boost/filesystem.hpp>
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;
// Progress callback: (bytesDownloaded, totalBytes)
typedef std::function<void(int64_t, int64_t)> ProgressCallback;
// Check if data dir already has blockchain data
bool NeedsBootstrap(const boost::filesystem::path& dataDir);
// Download a single file via HTTP GET, write to destPath
bool DownloadFile(const std::string& host, const std::string& urlPath,
const boost::filesystem::path& destPath,
ProgressCallback progressFn,
std::string& strError);
// Fetch the file manifest (list of relative paths to download)
bool FetchFileList(const std::string& host,
std::vector<std::string>& files,
std::string& strError);
// Download bootstrap.tar.gz and extract to dataDir.
// Falls back to filelist.txt + individual file download if tar.gz unavailable.
bool DownloadBootstrap(const std::string& host,
const boost::filesystem::path& dataDir,
ProgressCallback progressFn,
std::string& strError);
} // namespace Bootstrap
#endif // TRIANGLES_BOOTSTRAP_H
+1 -1
View File
@@ -8,7 +8,7 @@
// These need to be macros, as version.cpp's and triangles-qt.rc's voodoo requires it
#define CLIENT_VERSION_MAJOR 5
#define CLIENT_VERSION_MINOR 3
#define CLIENT_VERSION_REVISION 2
#define CLIENT_VERSION_REVISION 5
#define CLIENT_VERSION_BUILD 0
// Converts the parameter X to a string after macro replacement on X has been performed.
+48
View File
@@ -12,6 +12,7 @@
#include "checkpoints.h"
#include "smessage.h"
#include "openssl_compat.h"
#include "bootstrap.h"
#include "tor/tor_embedded.h"
#include "tor/onion_v3.h"
#include "tor/tor_process.h"
@@ -797,6 +798,43 @@ bool AppInit2()
for (string strDest : mapMultiArgs["-seednode"])
AddOneShot(strDest);
// ********************************************************* Step 6b: bootstrap download (daemon)
#ifndef QT_GUI
if (GetBoolArg("-bootstrap", false))
{
fs::path dataPath = GetDataDir();
std::string host = Bootstrap::DEFAULT_HOST;
std::string strError;
uiInterface.InitMessage(_("Downloading blockchain snapshot..."));
printf("Bootstrap: contacting %s...\n", host.c_str());
auto progressFn = [](int64_t bytesDownloaded, int64_t totalBytes) {
if (totalBytes > 0) {
printf("\rBootstrap: %lld / %lld MB (%lld%%)",
(long long)(bytesDownloaded / (1024*1024)),
(long long)(totalBytes / (1024*1024)),
(long long)((bytesDownloaded * 100) / totalBytes));
fflush(stdout);
}
};
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());
printf("Bootstrap: skipping, will sync from network.\n");
} else {
printf("\nBootstrap: done.\n");
}
}
#endif
// ********************************************************* Step 7: load blockchain
if (!bitdb.Open(GetDataDir()))
@@ -821,6 +859,16 @@ bool AppInit2()
if (!LoadBlockIndex())
return InitError(_("Error loading blkindex.dat"));
// If the block index is empty but blk0001.dat exists (bootstrap download),
// fast-import: build the index directly from the block file without re-writing
// data. Batches LevelDB commits every 200K blocks for speed.
if (nBestHeight == 0 && boost::filesystem::exists(GetDataDir() / "blk0001.dat")
&& mapBlockIndex.size() <= 1)
{
uiInterface.InitMessage(_("Importing bootstrap blocks..."));
printf("Block index empty but blk0001.dat exists - running fast import...\n");
FastImportBlockFile();
}
// as LoadBlockIndex can take several minutes, it's possible the user
// requested to kill triangles-qt during the last operation. If so, exit.
+241 -10
View File
@@ -3023,7 +3023,14 @@ bool LoadExternalBlockFile(FILE* fileIn)
{
int64_t nStart = GetTimeMillis();
// Get file size for progress reporting
int64_t nFileSize = 0;
fseek(fileIn, 0, SEEK_END);
nFileSize = ftell(fileIn);
fseek(fileIn, 0, SEEK_SET);
int nLoaded = 0;
int64_t nLastProgressReport = 0;
{
LOCK(cs_main);
try {
@@ -3074,6 +3081,20 @@ bool LoadExternalBlockFile(FILE* fileIn)
nLoaded++;
nPos += 4 + nSize;
}
// Report progress every 1000 blocks
if (nLoaded - nLastProgressReport >= 1000)
{
nLastProgressReport = nLoaded;
if (nFileSize > 0) {
int pct = (int)((int64_t)nPos * 100 / nFileSize);
printf("Importing blocks... %d blocks loaded (%d%%)\n", nLoaded, pct);
uiInterface.InitMessage(strprintf(_("Importing blocks... %d loaded (%d%%)"), nLoaded, pct));
} else {
printf("Importing blocks... %d blocks loaded\n", nLoaded);
uiInterface.InitMessage(strprintf(_("Importing blocks... %d loaded"), nLoaded));
}
}
}
}
catch (std::exception &e) {
@@ -3085,6 +3106,210 @@ bool LoadExternalBlockFile(FILE* fileIn)
return nLoaded > 0;
}
bool FastImportBlockFile()
{
// Fast block import: reads blk0001.dat and builds the block index
// directly without re-writing block data. LevelDB writes are batched
// every 200K blocks for speed. Only used for trusted bootstrap data
// (blocks below the hardcoded checkpoint).
fs::path blkPath = GetDataDir() / "blk0001.dat";
if (!fs::exists(blkPath))
return false;
printf("FastImportBlockFile: starting from %s\n", blkPath.string().c_str());
int64_t nStart = GetTimeMillis();
FILE* fileIn = fopen(blkPath.string().c_str(), "rb");
if (!fileIn)
return false;
// Get file size for progress
fseek(fileIn, 0, SEEK_END);
int64_t nFileSize = ftell(fileIn);
fseek(fileIn, 0, SEEK_SET);
int nLoaded = 0;
int64_t nLastProgressReport = 0;
{
LOCK(cs_main);
CAutoFile blkdat(fileIn, SER_DISK, CLIENT_VERSION);
CTxDB txdb;
txdb.TxnBegin();
unsigned int nPos = 0;
while (nPos != (unsigned int)-1 && blkdat.good() && !fRequestShutdown)
{
// Find message start bytes (same scan as LoadExternalBlockFile)
unsigned char pchData[65536];
do {
fseek(blkdat, nPos, SEEK_SET);
int nRead = fread(pchData, 1, sizeof(pchData), blkdat);
if (nRead <= 8)
{
nPos = (unsigned int)-1;
break;
}
void* nFind = memchr(pchData, pchMessageStart[0], nRead+1-sizeof(pchMessageStart));
if (nFind)
{
if (memcmp(nFind, pchMessageStart, sizeof(pchMessageStart))==0)
{
nPos += ((unsigned char*)nFind - pchData) + sizeof(pchMessageStart);
break;
}
nPos += ((unsigned char*)nFind - pchData) + 1;
}
else
nPos += sizeof(pchData) - sizeof(pchMessageStart) + 1;
} while(!fRequestShutdown);
if (nPos == (unsigned int)-1)
break;
fseek(blkdat, nPos, SEEK_SET);
unsigned int nSize;
blkdat >> nSize;
if (nSize == 0 || nSize > MAX_BLOCK_SIZE)
{
nPos += 4 + nSize;
continue;
}
// nBlockPos = file position where the block data starts
// (after 4-byte message start + 4-byte size)
unsigned int nBlockPos = nPos + 4;
CBlock block;
blkdat >> block;
uint256 hash = block.GetHash();
if (mapBlockIndex.count(hash))
{
nPos += 4 + nSize;
continue; // already indexed
}
// Create CBlockIndex
CBlockIndex* pindexNew = new CBlockIndex(1, nBlockPos, block);
if (!pindexNew)
break;
// Link to previous block
map<uint256, CBlockIndex*>::iterator miPrev = mapBlockIndex.find(block.hashPrevBlock);
if (miPrev != mapBlockIndex.end())
{
pindexNew->pprev = (*miPrev).second;
pindexNew->nHeight = pindexNew->pprev->nHeight + 1;
}
// Chain trust
pindexNew->nChainTrust = (pindexNew->pprev ? pindexNew->pprev->nChainTrust : 0) + pindexNew->GetBlockTrust();
// Stake entropy bit
pindexNew->SetStakeEntropyBit(block.GetStakeEntropyBit());
// Stake modifier (minimal for blocks far below checkpoint)
int nCheckpointHeight = Checkpoints::GetTotalBlocksEstimate();
if (pindexNew->nHeight >= nCheckpointHeight - 1000)
{
uint64_t nStakeModifier = 0;
bool fGeneratedStakeModifier = false;
ComputeNextStakeModifier(pindexNew->pprev, nStakeModifier, fGeneratedStakeModifier);
pindexNew->SetStakeModifier(nStakeModifier, fGeneratedStakeModifier);
}
else
{
pindexNew->SetStakeModifier(0, pindexNew->nHeight == 0);
}
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));
// Insert into mapBlockIndex
map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.insert(make_pair(hash, pindexNew)).first;
pindexNew->phashBlock = &((*mi).first);
// Link pnext for previous block
if (pindexNew->pprev)
pindexNew->pprev->pnext = pindexNew;
// Write block index to batch
txdb.WriteBlockIndex(CDiskBlockIndex(pindexNew));
// Build tx index entries
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++)
{
const CTransaction& tx = block.vtx[i];
CDiskTxPos posThisTx(1, nBlockPos, nTxPos);
txdb.UpdateTxIndex(tx.GetHash(), CTxIndex(posThisTx, tx.vout.size()));
nTxPos += ::GetSerializeSize(tx, SER_DISK, CLIENT_VERSION);
}
// Update best chain
if (pindexNew->nChainTrust > nBestChainTrust)
{
hashBestChain = hash;
pindexBest = pindexNew;
pblockindexFBBHLast = NULL;
nBestHeight = pindexNew->nHeight;
nBestChainTrust = pindexNew->nChainTrust;
nTimeBestReceived = GetTime();
}
// Set genesis block
if (pindexGenesisBlock == NULL && pindexNew->nHeight == 0)
pindexGenesisBlock = pindexNew;
nLoaded++;
nPos += 4 + nSize;
// Batch commit every 200K blocks for LevelDB efficiency
if (nLoaded % 200000 == 0)
{
txdb.WriteHashBestChain(hashBestChain);
txdb.TxnCommit();
txdb.TxnBegin();
}
// Report progress every 5000 blocks to keep GUI responsive.
// AppInit2 runs on the GUI thread, so uiInterface.InitMessage
// triggers processEvents() which prevents the window from freezing.
if (nLoaded % 5000 == 0)
{
int pct = (nFileSize > 0) ? (int)((int64_t)nPos * 100 / nFileSize) : 0;
printf("FastImport: %d blocks indexed (%d%%)\n", nLoaded, pct);
uiInterface.InitMessage(strprintf(_("Importing blocks... %d indexed (%d%%)"), nLoaded, pct));
}
}
// Final commit
if (pindexBest)
{
txdb.WriteHashBestChain(hashBestChain);
// Write sync checkpoint
Checkpoints::WriteSyncCheckpoint(hashBestChain);
}
txdb.TxnCommit();
}
nTransactionsUpdated++;
printf("FastImportBlockFile: indexed %d blocks in %"PRId64"ms\n", nLoaded, GetTimeMillis() - nStart);
return nLoaded > 0;
}
//////////////////////////////////////////////////////////////////////////////
//
// CAlert
@@ -3392,7 +3617,8 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
hashKey = Hash(BEGIN(hashKey), END(hashKey));
mapMix.insert(make_pair(hashKey, pnode));
}
int nRelayNodes = fReachable ? 2 : 1; // limited relaying of addresses outside our network(s)
// Small network: relay to more peers so addresses propagate quickly
int nRelayNodes = fReachable ? (int)mapMix.size() : 1;
for (multimap<uint256, CNode*>::iterator mi = mapMix.begin(); mi != mapMix.end() && nRelayNodes-- > 0; ++mi)
((*mi).second)->PushAddress(addr);
}
@@ -3496,11 +3722,12 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
// Trigger them to send a getblocks request for the next batch of inventory
if (inv.hash == pfrom->hashContinue)
{
// triangles: send latest proof-of-work block to allow the
// download node to accept as orphan (proof-of-stake
// block might be rejected by stake connection check)
// Send the best block hash to trigger the next getblocks.
// Original code sent the last PoW block, but since PoW ended
// at block 9000, that always sent an ancient block causing
// thousands of redundant round-trips through known blocks.
vector<CInv> vInv;
vInv.push_back(CInv(MSG_BLOCK, GetLastBlockIndex(pindexBest, false)->GetBlockHash()));
vInv.push_back(CInv(MSG_BLOCK, hashBestChain));
pfrom->PushMessage("inv", vInv);
pfrom->hashContinue = 0;
}
@@ -3548,7 +3775,11 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
// Send the rest of the chain
if (pindex)
pindex = pindex->pnext;
int nLimit = IsInitialBlockDownload() ? 20000 : 500;
// Send larger batches when the requester is far behind (syncing).
// The original check used our own IBD state, but we're the seed node
// (fully synced), so it always returned 500. Check how far behind
// the requester is instead.
int nLimit = (pindex && pindexBest && pindexBest->nHeight - pindex->nHeight > 1000) ? 10000 : 500;
printf("IBD-DIAG: getblocks request from peer %s: start=%d stop=%s limit=%d\n",
pfrom->addr.ToString().c_str(), (pindex ? pindex->nHeight : -1),
hashStop.ToString().substr(0,20).c_str(), nLimit);
@@ -3774,7 +4005,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
if (IsInitialBlockDownload())
{
static int nBlocksSinceRequest = 0;
if (++nBlocksSinceRequest >= 1000)
if (++nBlocksSinceRequest >= 5000)
{
nBlocksSinceRequest = 0;
pfrom->pindexLastGetBlocksBegin = NULL;
@@ -4230,7 +4461,7 @@ bool SendMessages(CNode* pto, bool fSendTrickle)
//
// Stall detection: if IBD and no new blocks for 5 seconds, re-request
// Stall detection: if IBD and no new blocks for 10 seconds, re-request
//
if (IsInitialBlockDownload() && !pto->fClient)
{
@@ -4240,8 +4471,8 @@ bool SendMessages(CNode* pto, bool fSendTrickle)
if (nBestHeight > nLastHeight) {
nLastHeight = nBestHeight;
nLastBlockReceived = GetTime();
} else if (nLastBlockReceived > 0 && GetTime() - nLastBlockReceived > 2) {
if (GetTime() - nLastStallLog >= 10) { // log every 10s max
} else if (nLastBlockReceived > 0 && GetTime() - nLastBlockReceived > 10) {
if (GetTime() - nLastStallLog >= 30) { // log every 30s max
printf("IBD-DIAG: STALL at height %d for %ds, peer=%s askfor_queue=%d send_size=%d\n",
nBestHeight, (int)(GetTime() - nLastBlockReceived),
pto->addr.ToString().c_str(),
+1
View File
@@ -112,6 +112,7 @@ CBlockIndex* FindBlockByHeight(int nHeight);
bool ProcessMessages(CNode* pfrom);
bool SendMessages(CNode* pto, bool fSendTrickle);
bool LoadExternalBlockFile(FILE* fileIn);
bool FastImportBlockFile();
bool CheckProofOfWork(uint256 hash, unsigned int nBits);
unsigned int GetNextTargetRequired(const CBlockIndex* pindexLast, bool fProofOfStake);
+8
View File
@@ -111,6 +111,7 @@ OBJS= \
obj/miner.o \
obj/main.o \
obj/net.o \
obj/bootstrap.o \
obj/net_bootstrap.o \
obj/protocol.o \
obj/trianglesrpc.o \
@@ -232,6 +233,13 @@ obj/tor_embedded.o: tor/tor_embedded.cpp
-e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \
rm -f $(@:%.o=%.d)
obj/bootstrap.o: bootstrap.cpp
$(CXX) -c $(xCXXFLAGS) -MMD -MF $(@:%.o=%.d) -o $@ $<
@cp $(@:%.o=%.d) $(@:%.o=%.P); \
sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \
-e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \
rm -f $(@:%.o=%.d)
obj/net_bootstrap.o: net_bootstrap.cpp
$(CXX) -c $(xCXXFLAGS) -MMD -MF $(@:%.o=%.d) -o $@ $<
@cp $(@:%.o=%.d) $(@:%.o=%.P); \
+8
View File
@@ -146,6 +146,7 @@ OBJS= \
obj/miner.o \
obj/main.o \
obj/net.o \
obj/bootstrap.o \
obj/net_bootstrap.o \
obj/protocol.o \
obj/trianglesrpc.o \
@@ -279,6 +280,13 @@ obj/tor_embedded.o: tor/tor_embedded.cpp
-e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \
rm -f $(@:%.o=%.d)
obj/bootstrap.o: bootstrap.cpp
$(CXX) -c $(xCXXFLAGS) -MMD -MF $(@:%.o=%.d) -o $@ $<
@cp $(@:%.o=%.d) $(@:%.o=%.P); \
sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \
-e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \
rm -f $(@:%.o=%.d)
obj/net_bootstrap.o: net_bootstrap.cpp
$(CXX) -c $(xCXXFLAGS) -MMD -MF $(@:%.o=%.d) -o $@ $<
@cp $(@:%.o=%.d) $(@:%.o=%.P); \
+11 -1
View File
@@ -551,13 +551,23 @@ void StakeMiner(CWallet *pwallet)
if (fTryToSync)
{
fTryToSync = false;
if (vNodes.size() < 3 || nBestHeight < GetNumBlocksOfPeers())
if (vNodes.size() < 1 || nBestHeight < GetNumBlocksOfPeers())
{
MilliSleep(60000);
continue;
}
}
//
// Update cached stake weight for UI display (avoids heavy work on UI thread)
//
{
uint64_t nMinWeight = 0, nMaxWeight = 0, nWeight = 0;
pwallet->GetStakeWeight(*pwallet, nMinWeight, nMaxWeight, nWeight);
pwallet->nCachedStakeWeight = nWeight;
pwallet->nCachedStakeWeightTime = GetTime();
}
//
// Create new block
//
+3 -3
View File
@@ -1584,7 +1584,7 @@ void ThreadOpenConnections2(void* parg)
for (CNode* pnode : vNodes) {
if (!pnode->fInbound) { fNoOutbound = false; break; }
}
if (fNoOutbound && (GetTime() - nStart > 30) && !fTestNet)
if (fNoOutbound && (GetTime() - nStart > 10) && !fTestNet)
{
std::vector<CAddress> vAdd;
for (unsigned int i = 0; i < ARRAYLEN(pnSeed); i++)
@@ -1596,7 +1596,7 @@ void ThreadOpenConnections2(void* parg)
vAdd.push_back(addr);
}
addrman.Add(vAdd, CNetAddr("127.0.0.1"));
printf("No outbound connections after 30s, added %d hardcoded seeds\n", (int)vAdd.size());
printf("No outbound connections after 10s, added %d hardcoded seeds\n", (int)vAdd.size());
}
}
@@ -1642,7 +1642,7 @@ void ThreadOpenConnections2(void* parg)
continue;
// only consider very recently tried nodes after 30 failed attempts
if (nANow - addr.nLastTry < 600 && nTries < 30)
if (nANow - addr.nLastTry < 120 && nTries < 30)
continue;
// do not allow non-default ports, unless after 50 invalid addresses selected already
+1 -1
View File
@@ -422,7 +422,7 @@ public:
// the key is the earliest time the request can be sent
int64_t& nRequestTime = mapAlreadyAskedFor[inv];
if (fDebugNet)
printf("askfor %s %"PRId64" (%s)\n", inv.ToString().c_str(), nRequestTime, DateTimeStrFormat("%H:%M:%S", nRequestTime/1000000).c_str());
printf("askfor %s %" PRId64 " (%s)\n", inv.ToString().c_str(), nRequestTime, DateTimeStrFormat("%H:%M:%S", nRequestTime/1000000).c_str());
// Make sure not to reuse time indexes to keep things in the same order
int64_t nNow = (GetTime() - 1) * 1000000;
+20 -5
View File
@@ -15,7 +15,7 @@ static const int64_t nClientStartupTime = GetTime();
ClientModel::ClientModel(OptionsModel *optionsModel, QObject *parent) :
QObject(parent), optionsModel(optionsModel),
cachedNumBlocks(0), cachedNumBlocksOfPeers(0), pollTimer(0)
cachedNumBlocks(0), cachedNumBlocksOfPeers(0), cachedNumConnections(0), pollTimer(0)
{
numBlocksAtStartup = -1;
@@ -34,7 +34,14 @@ ClientModel::~ClientModel()
int ClientModel::getNumConnections() const
{
return vNodes.size();
// Use TRY_LOCK to avoid blocking the UI thread when the network
// thread holds cs_vNodes (e.g. during DNS resolution or connections).
// Return the cached value if the lock is busy.
TRY_LOCK(cs_vNodes, lockNodes);
if (lockNodes) {
cachedNumConnections = vNodes.size();
}
return cachedNumConnections;
}
int ClientModel::getNumBlocks() const
@@ -63,12 +70,20 @@ void ClientModel::updateTimer()
int newNumBlocks = getNumBlocks();
int newNumBlocksOfPeers = getNumBlocksOfPeers();
// Always emit during IBD so the speed/ETA display stays live
if(cachedNumBlocks != newNumBlocks || cachedNumBlocksOfPeers != newNumBlocksOfPeers
|| newNumBlocks < newNumBlocksOfPeers)
// Always emit when values change or during IBD.
// Also emit every ~30 seconds even when idle so setNumBlocks() can
// re-evaluate sync status (e.g. when a new block arrives after a long gap).
static int64_t nLastEmit = 0;
int64_t nNow = GetTime();
bool fChanged = (cachedNumBlocks != newNumBlocks || cachedNumBlocksOfPeers != newNumBlocksOfPeers);
bool fCatchingUp = (newNumBlocks < newNumBlocksOfPeers);
bool fPeriodicRefresh = (nNow - nLastEmit >= 30);
if(fChanged || fCatchingUp || fPeriodicRefresh)
{
cachedNumBlocks = newNumBlocks;
cachedNumBlocksOfPeers = newNumBlocksOfPeers;
nLastEmit = nNow;
emit numBlocksChanged(newNumBlocks, newNumBlocksOfPeers);
}
+1
View File
@@ -49,6 +49,7 @@ private:
int cachedNumBlocks;
int cachedNumBlocksOfPeers;
mutable int cachedNumConnections;
int numBlocksAtStartup;
+1 -1
View File
@@ -2,7 +2,7 @@
#define GUICONSTANTS_H
/* Milliseconds between model updates */
static const int MODEL_UPDATE_DELAY = 500;
static const int MODEL_UPDATE_DELAY = 2500;
/* AskPassphraseDialog -- Maximum passphrase length */
static const int MAX_PASSPHRASE_SIZE = 1024;
+72
View File
@@ -1,5 +1,6 @@
#include "introdialog.h"
#include "util.h"
#include "bootstrap.h"
#include <QSettings>
#include <QVBoxLayout>
@@ -9,6 +10,9 @@
#include <QDir>
#include <QMessageBox>
#include <QDialogButtonBox>
#include <QProgressDialog>
#include <QCheckBox>
#include <QApplication>
#include <boost/filesystem.hpp>
@@ -201,5 +205,73 @@ bool IntroDialog::pickDataDirectory()
return false;
}
// Offer bootstrap download on each startup (unless user checked "don't ask again")
fs::path dataDirPath(dataDir.toStdString());
if (!settings.value("bootstrapDontAsk", false).toBool())
{
QMessageBox msgBox;
msgBox.setWindowTitle("Triangles");
msgBox.setText(
"Would you like to download the latest blockchain snapshot?\n\n"
"This will download the blockchain data from the Triangles network "
"and replace any existing chain data in your data directory.\n\n"
"Click Yes to download, or No to sync from the network.");
msgBox.setIcon(QMessageBox::Question);
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
msgBox.setDefaultButton(QMessageBox::Yes);
QCheckBox *dontAskBox = new QCheckBox("Don't show this again");
msgBox.setCheckBox(dontAskBox);
int ret = msgBox.exec();
if (dontAskBox->isChecked())
settings.setValue("bootstrapDontAsk", true);
if (ret == QMessageBox::Yes)
{
std::string host = Bootstrap::DEFAULT_HOST;
std::string strError;
QProgressDialog progress("Downloading blockchain snapshot...", "Cancel",
0, 100, 0);
progress.setWindowTitle("Triangles - Bootstrap");
progress.setWindowModality(Qt::ApplicationModal);
progress.setMinimumDuration(0);
progress.setValue(0);
auto progressFn = [&progress](int64_t bytesDownloaded, int64_t totalBytes) {
if (totalBytes > 0) {
int pct = (int)((bytesDownloaded * 100) / totalBytes);
progress.setValue(pct);
progress.setLabelText(
QString("Downloading blockchain snapshot... %1 MB / %2 MB")
.arg(bytesDownloaded / (1024*1024))
.arg(totalBytes / (1024*1024)));
} else {
progress.setLabelText(
QString("Downloading blockchain snapshot... %1 MB")
.arg(bytesDownloaded / (1024*1024)));
}
QApplication::processEvents();
};
bool success = Bootstrap::DownloadBootstrap(host, dataDirPath, progressFn, strError);
if (!success) {
host = Bootstrap::FALLBACK_HOST;
progress.setValue(0);
success = Bootstrap::DownloadBootstrap(host, dataDirPath, progressFn, strError);
}
if (!success) {
QMessageBox::warning(0, "Triangles",
QString("Could not download blockchain snapshot:\n%1\n\n"
"The wallet will sync from the network instead.")
.arg(QString::fromStdString(strError)));
} else {
progress.setValue(100);
}
}
}
return true;
}
+2
View File
@@ -126,6 +126,8 @@ OverviewPage::~OverviewPage()
void OverviewPage::setBalance(qint64 balance, qint64 stake, qint64 unconfirmedBalance, qint64 immatureBalance)
{
if (!model || !model->getOptionsModel())
return;
int unit = model->getOptionsModel()->getDisplayUnit();
currentBalance = balance;
currentStake = stake;
+10 -2
View File
@@ -71,7 +71,13 @@ public:
OutputDebugStringF("refreshWallet\n");
cachedWallet.clear();
{
LOCK(wallet->cs_wallet);
TRY_LOCK(wallet->cs_wallet, lockWallet);
if(!lockWallet)
{
// Lock busy (block processing), retry in 500ms
QTimer::singleShot(500, parent, SLOT(refreshWallet()));
return;
}
for(std::map<uint256, CWalletTx>::iterator it = wallet->mapWallet.begin(); it != wallet->mapWallet.end(); ++it)
{
if(TransactionRecord::showTransaction(it->second))
@@ -89,7 +95,9 @@ public:
{
OutputDebugStringF("updateWallet %s %i\n", hash.ToString().c_str(), status);
{
LOCK(wallet->cs_wallet);
TRY_LOCK(wallet->cs_wallet, lockWallet);
if (!lockWallet)
return;
// Find transaction in wallet
std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(hash);
+43 -24
View File
@@ -799,19 +799,23 @@ void TrianglesGUI::setNumConnections(int count)
void TrianglesGUI::setNumBlocks(int count, int nTotalBlocks)
{
// don't show / hide progress bar and its label if we have no connection to the network
if (!clientModel || clientModel->getNumConnections() == 0)
if (!clientModel)
return;
int nConnections = clientModel->getNumConnections();
// Hide progress bar when disconnected, but don't return early -
// we still need to update sync state and the out-of-sync warning
if (nConnections == 0)
{
progressBarLabel->setVisible(false);
progressBar->setVisible(false);
ui->label_blocks->setVisible(false);
return;
}
QString tooltip;
if(count < nTotalBlocks)
if(nConnections > 0 && count < nTotalBlocks)
{
// Calculate blocks/sec - only update rate when new blocks arrive
static int lastCount = 0;
@@ -899,20 +903,20 @@ void TrianglesGUI::setNumBlocks(int count, int nTotalBlocks)
text = tr("%n day(s) ago","",secs/(60*60*24));
}
// Set icon state: spinning if catching up, tick otherwise
if(secs < 90*60 && count >= nTotalBlocks)
// Set icon state: spinning if catching up, tick otherwise.
// For PoS chains with few stakers, blocks can be hours or days apart.
// Sync status is based purely on block count - NOT block timestamp.
// A stale chain (no recent blocks) is still "synced" if we have all blocks.
if(count >= nTotalBlocks)
{
tooltip = tr("Up to date") + QString(".<br>") + tooltip;
labelBlocksIcon->setPixmap(QIcon(":/icons/synced").pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE));
overviewPage->showOutOfSyncWarning(false);
}
else
{
tooltip = tr("Catching up...") + QString("<br>") + tooltip;
//syncIconMovie doesn't work for some reason - using fallback png
//labelBlocksIcon->setMovie(syncIconMovie);
//syncIconMovie->start();
labelBlocksIcon->setPixmap(QIcon(":/icons/notsynced").pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE));
overviewPage->showOutOfSyncWarning(true);
@@ -1590,32 +1594,47 @@ void TrianglesGUI::toggleHidden()
void TrianglesGUI::updateStakingIcon()
{
uint64_t nMinWeight = 0, nMaxWeight = 0, nWeight = 0;
// Read cached staking info computed by the staking thread.
// No locks needed - these are volatile values written by the miner thread
// and are display-only. This keeps the UI thread completely non-blocking.
uint64_t nWeight = 0;
bool fWalletLocked = false;
bool fHasPeers = false;
if (pwalletMain)
pwalletMain->GetStakeWeight(*pwalletMain, nMinWeight, nMaxWeight, nWeight);
{
TRY_LOCK(pwalletMain->cs_wallet, lockWallet);
if (lockWallet)
fWalletLocked = pwalletMain->IsLocked();
else
return; // Skip this cycle, try again in 30 seconds
// Use cached weight from the staking thread instead of computing on UI thread.
// The staking thread updates this every ~500ms-1s loop iteration.
nWeight = pwalletMain->nCachedStakeWeight;
}
{
TRY_LOCK(cs_vNodes, lockNodes);
if (lockNodes)
fHasPeers = !vNodes.empty();
}
if (nLastCoinStakeSearchInterval && nWeight)
{
uint64_t nNetworkWeight = GetPoSKernelPS();
unsigned nEstimateTime = nTargetSpacing * nNetworkWeight / nWeight;
unsigned nEstimateTime = nWeight > 0 ? nTargetSpacing * nNetworkWeight / nWeight : 0;
QString text;
if (nEstimateTime < 60)
{
text = tr("%n second(s)", "", nEstimateTime);
}
else if (nEstimateTime < 60*60)
{
text = tr("%n minute(s)", "", nEstimateTime/60);
}
else if (nEstimateTime < 24*60*60)
{
text = tr("%n hour(s)", "", nEstimateTime/(60*60));
}
else
{
text = tr("%n day(s)", "", nEstimateTime/(60*60*24));
}
labelStakingIcon->setPixmap(QIcon(":/icons/staking_on").pixmap(STATUSBAR_ICONSIZE,STATUSBAR_ICONSIZE));
labelStakingIcon->setToolTip(tr("Staking.<br>Your weight is %1<br>Network weight is %2<br>Expected time to earn reward is %3").arg(nWeight).arg(nNetworkWeight).arg(text));
@@ -1623,9 +1642,9 @@ void TrianglesGUI::updateStakingIcon()
else
{
labelStakingIcon->setPixmap(QIcon(":/icons/staking_off").pixmap(STATUSBAR_ICONSIZE,STATUSBAR_ICONSIZE));
if (pwalletMain && pwalletMain->IsLocked())
if (fWalletLocked)
labelStakingIcon->setToolTip(tr("Not staking because wallet is locked"));
else if (vNodes.empty())
else if (!fHasPeers)
labelStakingIcon->setToolTip(tr("Not staking because wallet is offline"));
else if (IsInitialBlockDownload())
labelStakingIcon->setToolTip(tr("Not staking because wallet is syncing"));
+22 -11
View File
@@ -86,10 +86,12 @@ void WalletModel::pollBalanceChanged()
void WalletModel::checkBalanceChanged()
{
qint64 newBalance = getBalance();
qint64 newStake = getStake();
qint64 newUnconfirmedBalance = getUnconfirmedBalance();
qint64 newImmatureBalance = getImmatureBalance();
// Get all balances in a single lock acquisition + single pass.
// Uses TRY_LOCK internally - if cs_wallet is busy (block processing),
// skip this cycle. The timer will retry in 2.5 seconds.
int64_t newBalance = 0, newStake = 0, newUnconfirmedBalance = 0, newImmatureBalance = 0;
if (!wallet->GetAllBalances(newBalance, newStake, newUnconfirmedBalance, newImmatureBalance))
return;
if(cachedBalance != newBalance || cachedStake != newStake || cachedUnconfirmedBalance != newUnconfirmedBalance || cachedImmatureBalance != newImmatureBalance)
{
@@ -106,14 +108,20 @@ void WalletModel::updateTransaction(const QString &hash, int status)
if(transactionTableModel)
transactionTableModel->updateTransaction(hash, status);
// Balance and number of transactions might have changed
checkBalanceChanged();
// Don't call checkBalanceChanged() here - it does LOCK(cs_wallet) + iterates
// all wallet transactions, blocking the UI thread. The pollBalanceChanged()
// timer already handles balance updates every 2.5 seconds with TRY_LOCK.
int newNumTransactions = getNumTransactions();
if(cachedNumTransactions != newNumTransactions)
// Same for getNumTransactions() - use cached count from the transaction model
// to avoid another LOCK(cs_wallet) on the UI thread.
if(transactionTableModel)
{
cachedNumTransactions = newNumTransactions;
emit numTransactionsChanged(newNumTransactions);
int newNumTransactions = transactionTableModel->rowCount(QModelIndex());
if(cachedNumTransactions != newNumTransactions)
{
cachedNumTransactions = newNumTransactions;
emit numTransactionsChanged(newNumTransactions);
}
}
}
@@ -240,7 +248,10 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(const QList<SendCoinsRecipie
if(!fCreated)
{
if((total + nFeeRequired) > nBalance) // FIXME: could cause collisions in the future
// NOTE: Potential edge case in fee calculation. The term "collisions" is unclear
// from original comment - may refer to transaction conflicts or UTXO selection issues.
// Consider reviewing Bitcoin Core's current implementation of this balance check.
if((total + nFeeRequired) > nBalance)
{
return SendCoinsReturn(AmountWithFeeExceedsBalance, nFeeRequired);
}
+6 -2
View File
@@ -229,7 +229,8 @@ Value getworkex(const Array& params, bool fHelp)
if(coinbase.size() == 0)
pblock->vtx[0].vin[0].scriptSig = mapNewBlock[pdata->hashMerkleRoot].second;
else
CDataStream(coinbase, SER_NETWORK, PROTOCOL_VERSION) >> pblock->vtx[0]; // FIXME - DRM!
// Deserialize custom coinbase transaction from miner
CDataStream(coinbase, SER_NETWORK, PROTOCOL_VERSION) >> pblock->vtx[0];
pblock->hashMerkleRoot = pblock->BuildMerkleTree();
@@ -260,7 +261,10 @@ Value getwork(const Array& params, bool fHelp)
throw JSONRPCError(RPC_MISC_ERROR, "No more PoW blocks");
typedef map<uint256, pair<CBlock*, CScript> > mapNewBlock_t;
static mapNewBlock_t mapNewBlock; // FIXME: thread safety
// NOTE: Thread safety issue - static variables accessed by multiple RPC threads
// without mutex protection. Low priority since PoW ended at block 9000 and
// getwork is rarely used. Consider adding std::mutex if usage increases.
static mapNewBlock_t mapNewBlock;
static vector<CBlock*> vNewBlock;
static CReserveKey reservekey(pwalletMain);
+1 -1
View File
@@ -237,7 +237,7 @@ void runCommand(std::string strCommand);
inline std::string i64tostr(int64_t n)
{
return strprintf("%"PRId64, n);
return strprintf("%" PRId64, n);
}
inline std::string itostr(int n)
+1 -1
View File
@@ -53,7 +53,7 @@ static const int MEMPOOL_GD_VERSION = 60002;
#define DISPLAY_VERSION_MAJOR 5
#define DISPLAY_VERSION_MINOR 3
#define DISPLAY_VERSION_REVISION 2
#define DISPLAY_VERSION_REVISION 5
#define DISPLAY_VERSION_BUILD 0
#endif
+38
View File
@@ -958,11 +958,21 @@ int CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate)
CBlockIndex* pindex = pindexStart;
{
LOCK(cs_wallet);
int nScanned = 0;
int nTotal = nBestHeight - (pindexStart ? pindexStart->nHeight : 0);
if (nTotal < 1) nTotal = 1;
while (pindex)
{
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))) {
@@ -1466,6 +1476,34 @@ int64_t CWallet::GetNewMint() const
return nTotal;
}
bool CWallet::GetAllBalances(int64_t& nBalance, int64_t& nStake, int64_t& nUnconfirmed, int64_t& nImmature) const
{
nBalance = 0;
nStake = 0;
nUnconfirmed = 0;
nImmature = 0;
TRY_LOCK(cs_wallet, lockWallet);
if (!lockWallet)
return false;
for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
{
const CWalletTx& pcoin = (*it).second;
if (pcoin.IsCoinStake() && pcoin.GetBlocksToMaturity() > 0 && pcoin.GetDepthInMainChain() > 0)
nStake += CWallet::GetCredit(pcoin);
if (pcoin.IsCoinBase() && pcoin.GetBlocksToMaturity() > 0 && pcoin.IsInMainChain())
nImmature += GetCredit(pcoin);
if (pcoin.IsTrusted())
nBalance += pcoin.GetAvailableCredit();
if (!pcoin.IsFinal() || !pcoin.IsTrusted())
nUnconfirmed += pcoin.GetAvailableCredit();
}
return true;
}
bool CWallet::SelectCoinsMinConf(int64_t nTargetValue, unsigned int nSpendTime, int nConfMine, int nConfTheirs, vector<COutput> vCoins, set<pair<const CWalletTx*,unsigned int> >& setCoinsRet, int64_t& nValueRet) const
{
setCoinsRet.clear();
+11
View File
@@ -7,6 +7,7 @@
#include <string>
#include <vector>
#include <atomic>
#include <stdlib.h>
@@ -107,6 +108,8 @@ public:
nMasterKeyMaxID = 0;
pwalletdbEncryption = NULL;
nOrderPosNext = 0;
nCachedStakeWeight = 0;
nCachedStakeWeightTime = 0;
}
CWallet(std::string strWalletFileIn)
{
@@ -117,6 +120,8 @@ public:
nMasterKeyMaxID = 0;
pwalletdbEncryption = NULL;
nOrderPosNext = 0;
nCachedStakeWeight = 0;
nCachedStakeWeightTime = 0;
}
std::map<uint256, CWalletTx> mapWallet;
@@ -190,6 +195,8 @@ public:
int64_t GetImmatureBalance() const;
int64_t GetStake() const;
int64_t GetNewMint() const;
// Get all balances in a single lock acquisition + single pass (avoids 4x lock + 4x iteration)
bool GetAllBalances(int64_t& nBalance, int64_t& nStake, int64_t& nUnconfirmed, int64_t& nImmature) const;
bool CreateTransaction(const std::vector<std::pair<CScript, int64_t> >& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, int64_t& nFeeRet, const CCoinControl *coinControl=NULL);
bool CreateTransaction(CScript scriptPubKey, int64_t nValue, std::string& sNarr, CWalletTx& wtxNew, CReserveKey& reservekey, int64_t& nFeeRet, const CCoinControl *coinControl=NULL);
bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey);
@@ -197,6 +204,10 @@ public:
bool GetStakeWeight(const CKeyStore& keystore, uint64_t& nMinWeight, uint64_t& nMaxWeight, uint64_t& nWeight);
bool CreateCoinStake(const CKeyStore& keystore, unsigned int nBits, int64_t nSearchInterval, int64_t nFees, CTransaction& txNew, CKey& key);
// Cached staking info - updated by the staking thread, read by the UI thread.
std::atomic<uint64_t> nCachedStakeWeight;
std::atomic<int64_t> nCachedStakeWeightTime; // GetTime() when last updated
std::string SendMoney(CScript scriptPubKey, int64_t nValue, std::string& sNarr, CWalletTx& wtxNew, bool fAskFee=false);
std::string SendMoneyToDestination(const CTxDestination& address, int64_t nValue, std::string& sNarr, CWalletTx& wtxNew, bool fAskFee=false);
+2
View File
@@ -204,6 +204,7 @@ HEADERS += src/qt/trianglesgui.h \
src/qt/addressbookpage.h \
src/qt/aboutdialog.h \
src/qt/introdialog.h \
src/bootstrap.h \
src/qt/editaddressdialog.h \
src/qt/trianglesaddressvalidator.h \
src/alert.h \
@@ -322,6 +323,7 @@ SOURCES += src/qt/triangles.cpp src/qt/trianglesgui.cpp \
src/qt/addressbookpage.cpp \
src/qt/aboutdialog.cpp \
src/qt/introdialog.cpp \
src/bootstrap.cpp \
src/qt/editaddressdialog.cpp \
src/qt/trianglesaddressvalidator.cpp \
# Old embedded Tor v2 client removed - incompatible with OpenSSL 3.x