Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 65b9417c28 | |||
| ed87543153 | |||
| 6e9dbb1aa9 | |||
| a6ec711cfa | |||
| 6877aeaddb | |||
| 60067e1a88 | |||
| e91ccd8786 | |||
| 96fb7d5040 | |||
| 47cf8abbda | |||
| 2abd494fec | |||
| 378b0370e3 |
@@ -9,7 +9,7 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
VERSION: "5.2.0"
|
||||
VERSION: "5.3.5"
|
||||
|
||||
jobs:
|
||||
build-windows-qt:
|
||||
|
||||
@@ -52,3 +52,6 @@ triangles.conf
|
||||
*.key
|
||||
*.cert
|
||||
*.gpg
|
||||
*.o
|
||||
src/trianglesd
|
||||
src/obj/
|
||||
|
||||
@@ -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,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
|
||||
|
||||
|
||||
@@ -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
@@ -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"
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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
@@ -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(),
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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); \
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ private:
|
||||
|
||||
int cachedNumBlocks;
|
||||
int cachedNumBlocksOfPeers;
|
||||
mutable int cachedNumConnections;
|
||||
|
||||
int numBlocksAtStartup;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user