Compare commits

..

8 Commits

Author SHA1 Message Date
sami7777 a671708f0b Anti-fork hardening: faster convergence for small Tor-only network (v5.8.3)
Build All Platforms / test-linux-unit (push) Failing after 40s
Build All Platforms / build-linux-qt (push) Failing after 40s
Build All Platforms / build-linux-daemon (push) Failing after 40s
Build All Platforms / build-windows-qt (push) Has been cancelled
Build All Platforms / build-windows-daemon (push) Has been cancelled
Build All Platforms / build-macos (push) Has been cancelled
Build All Platforms / release (push) Has been cancelled
Build All Platforms / Trigger TRI-PI ARM64 Build (push) Has been cancelled
- Reduce equal-trust reorg cooldown from 10min to 2min for faster convergence
- Tighten future block drift from 3min to 90sec to shrink competing-block window
- Require 2+ peers before staking (was 1) to prevent isolated fork creation
- Push full blocks directly to peers instead of inv-only (saves 1-2s Tor roundtrip)
- Add periodic 45-second chain-tip sync to detect and resolve silent forks

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-19 19:32:52 -07:00
sami7777 be90d39cd4 Add direct TCP bootstrap downloads and HTTP redirect handling
Bootstrap server is on clearnet, so bypass Tor SOCKS proxy for faster
downloads. Adds redirect following (301/302/307/308) with safety limits.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-19 18:11:46 -07:00
sami7777 4d0478add5 Fix build error: fWalletUnlockStakingOnly is a global variable, not CWallet member 2026-04-19 02:41:42 -07:00
sami7777 734979c93b Fix critical stability issues (v5.8.2 stability patch)
Build All Platforms / test-linux-unit (push) Failing after 40s
Build All Platforms / build-linux-qt (push) Failing after 40s
Build All Platforms / build-linux-daemon (push) Failing after 40s
Build All Platforms / build-windows-qt (push) Has been cancelled
Build All Platforms / build-windows-daemon (push) Has been cancelled
Build All Platforms / build-macos (push) Has been cancelled
Build All Platforms / release (push) Has been cancelled
Build All Platforms / Trigger TRI-PI ARM64 Build (push) Has been cancelled
Critical fixes for production stability:

1. NULL POINTER CRASH FIXES (P0)
   - Add defensive null checks in GetNextTargetRequired_()
   - Fix GetDifficulty() crash when no PoW blocks exist
   - Prevents seed node crash-loops and RPC failures

2. CHAIN REORGANIZATION ATOMICITY (P0)
   - Move setStakeSeen modifications to AFTER database commit
   - Prevents DB/memory state desync on failed reorgs
   - Adds critical transaction boundary documentation
   - Improves reorg logging with fork depth details

3. ORPHAN BLOCK MEMORY MANAGEMENT (P1)
   - Extract LimitOrphanBlocks() into reusable function
   - Add proactive cleanup when IBD completes (4000→2000 limit)
   - Prevents memory exhaustion DoS attacks
   - Better diagnostic logging

4. DATABASE ERROR HANDLING (P2)
   - Enhanced critical error messages in TxnCommit()
   - Clear guidance on disk/corruption/permissions issues
   - Faster incident diagnosis

All changes are consensus-safe with no fork risk.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-04-19 02:31:55 -07:00
sami7777 00af636aca Update seed nodes and enable parallel block downloads
- Updated README.md with current onion seed nodes
- Increased header download window from 128 to 512
- Added parallel block downloading across multiple peers
- Improved sync performance with redundant request timeouts

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-04-19 02:09:28 -07:00
sami7777 9377b3a52f Release v5.8.2: Anti-fork fixes, staking diagnostics, and performance improvements
Version System:
- Unified version display as v5.8.2 (removed trailing .0)
- Single source of truth in clientversion.h
- Fixed version.cpp to use CLIENT_VERSION_* macros

Staking Improvements:
- Enhanced getstakinginfo with detailed diagnostics
- Shows specific reasons when staking is disabled
- Added wallet lock status, mature coins check, peer count

Performance & Sync:
- Added checkpoint at block 2,200,000 (hash: 0a8d0442...)
- 14 total checkpoints for faster sync
- Enhanced recalculatesupply RPC with safety validation
- Prevents changes > 1M TRI, fixes money supply tracking

Anti-Fork Protection:
- Enhanced reorganize logging with fork details
- Shows old/new tips, fork point, disconnect/connect counts
- Works with existing anti-oscillation and chain re-eval fixes

Recovery Tools (Krystie):
- -reindex flag for full block index rebuild
- recalculatesupply RPC to fix money supply from UTXOs
- SumUtxoValues() helper for UTXO set analysis

All changes are non-consensus and wallet-safe.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-Authored-By: Krystie <krystie@cryptographic-triangles.org>
2026-04-19 02:09:12 -07:00
Krystie 1881ff867e Auto-backup wallet.dat before flush/rewrite operations
- Add AutoBackupWallet() that copies wallet.dat to wallet.dat.auto.bak
  before any DB flush or rewrite
- Call AutoBackupWallet() in ThreadFlushWalletDB() before flushing
- Call AutoBackupWallet() in AppInit2() after loading wallet
- Add suspicious-size check in AppInit2() (warns if wallet.dat < 1KB)
- Declare AutoBackupWallet() in db.h

This protects against wallet corruption during crash by maintaining
an auto-backup that is always at least as recent as the last flush.
2026-04-18 23:34:19 -07:00
Krystie caddfb1789 Add checkpoints to 2.2M+, bump orphan limit to 2000, add modernization roadmap
- Add mainnet+testnet checkpoints at blocks 2190000, 2200000, 2205000
- Bump MAX_ORPHAN_BLOCKS from 750 to 2000 (prevents fork deadlocks)
- Add MODERNIZATION_ROADMAP.md with prioritized improvement plan

These changes prevent the exact fork deadlock that happened during
the Apr 17-19 incident: post-IBD orphan limit of 750 was too low,
causing nodes to deadlock when divergent blocks arrived.
2026-04-18 19:30:37 -07:00
21 changed files with 796 additions and 105 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ if(POLICY CMP0167)
endif()
project(Triangles
VERSION 5.8.1.0
VERSION 5.8.3
DESCRIPTION "Cryptographic Triangles Wallet"
LANGUAGES C CXX
)
+210
View File
@@ -0,0 +1,210 @@
# Triangles Modernization Roadmap
**Goal:** Make TRI faster to sync, safer for wallets, and more useful as a currency — without breaking consensus.
**Invariant:** Any change that modifies block validation, stake modifier computation, transaction format, or signature verification MUST preserve exact consensus with existing v5.x nodes. When in doubt, test against a synced v5.8.1 node.
---
## Priority 1: Faster Syncing (High Impact, Low Risk)
### 1.1 Update Checkpoints (Easy, Immediate)
**Problem:** Last hardcoded checkpoint is at block 2,186,940. `IsInitialBlockDownload()` returns false past this point, causing orphan limit to drop from 4000 to 750 — exactly what caused the fork deadlock.
**Fix:** Add checkpoints every ~50,000 blocks up to current height (~2,207,000+).
```cpp
// src/checkpoints.cpp - add recent checkpoints
{2190000, uint256("...")},
{2195000, uint256("...")},
{2200000, uint256("...")},
{2205000, uint256("...")},
{2210000, uint256("...")},
```
**Risk:** None — checkpoints are only used for IBD detection and quick rejection of clearly wrong chains.
### 1.2 Increase Post-Checkpoint Orphan Limit (Easy)
**Problem:** 750 orphans after IBD is too low for a low-peer network. During the fork incident, 750 orphans filled up and the node deadlocked.
**Fix:**
```cpp
// src/main.h
static const unsigned int MAX_ORPHAN_BLOCKS = 2000; // was 750
```
**Risk:** Slightly more memory usage during forks. Worth it for resilience.
### 1.3 Parallel Block Download (Medium Effort)
**Problem:** Current implementation downloads blocks sequentially from one peer at a time during IBD.
**Fix:** Increase batch sizes and allow concurrent block downloads from multiple peers:
```cpp
// src/main.cpp
// During IBD, request blocks from multiple peers simultaneously
unsigned int nGetDataBatchSize = IsInitialBlockDownload() ? 8000 : 1000; // was 4000
```
**Risk:** Low — larger batch sizes are already proven in Bitcoin forks.
### 1.4 Header-First Sync (Medium Effort)
**Problem:** Node downloads full blocks before validating headers. A bad peer can waste bandwidth.
**Fix:** Download and validate all headers first (compact ~80 bytes each), then download full blocks only for the best chain.
- Separate `getheaders`/`headers` message handling
- Download blocks only for the best header chain
- Reduces wasted bandwidth during forks by 95%+
### 1.5 Bootstrap Over HTTPS with Resume (Easy)
**Problem:** Built-in bootstrap (`-bootstrap`) uses raw TCP and can't resume interrupted downloads.
**Fix:** The existing `bootstrap.cpp` already supports downloading. Add:
- Resume support (Range headers)
- SHA256 verification of downloaded archive
- Better progress reporting
- Fallback mirrors
---
## Priority 2: Wallet Safety (Critical)
### 2.1 Automatic Wallet Backup Before Dangerous Operations (Easy)
**Problem:** Corrupt wallet = lost funds. No automatic backup before risky operations.
**Fix:** In `walletdb.cpp`, before any rewrite:
```cpp
// Before wallet.dat rewrite, copy to wallet.dat.bak
if (boost::filesystem::exists(pathWallet)) {
boost::filesystem::copy_file(pathWallet, pathWallet + ".bak",
boost::filesystem::copy_option::overwrite_if_exists);
}
```
### 2.2 Detect and Report BDB Corruption (Easy)
**Problem:** BDB corruption silently corrupts wallet. User doesn't know until it's too late.
**Fix:** Add wallet integrity check on load:
```cpp
// In CWallet::LoadWallet()
// After opening, verify BDB environment is healthy
// If DB_RUNRECOVERY, auto-salvage and warn user
```
### 2.3 Wallet.dat Versioning (Medium Effort)
**Problem:** Single wallet.dat file. If it corrupts during write, funds are lost.
**Fix:** Implement copy-on-write wallet saves:
- Write new wallet data to `wallet.dat.new`
- Atomically rename `wallet.dat``wallet.dat.old`, `wallet.dat.new``wallet.dat`
- Keep last 3 wallet revisions
- On load, try wallet.dat first, fall back to wallet.dat.old if corrupt
### 2.4 Seed Phrase / HD Wallet (High Effort, High Impact)
**Problem:** Losing wallet.dat = losing everything. No recovery mechanism.
**Fix:** Implement BIP39/BIP44 HD wallet as optional upgrade:
- Generate 12/24-word seed phrase on new wallet creation
- Derive all keys from seed deterministically
- Import seed on any device to recover wallet
- Keep backward compatibility with existing non-HD wallets
---
## Priority 3: Network Resilience (Medium Impact)
### 3.1 Better Peer Management (Medium Effort)
**Problem:** Low peer counts (2-6) lead to fork divergence. No prioritization of reliable peers.
**Fix:**
- Peer reliability scoring (track which peers provide valid blocks)
- Prefer peers that are ahead and on the same chain
- Automatic disconnection of stale/forked peers
- Increase default `maxconnections` from 64 to 128
### 3.2 Compact Block Relay (High Effort)
**Problem:** Full blocks are sent even when the receiver likely already has most transactions.
**Fix:** Implement BIP 152 compact blocks:
- Send block header + short transaction IDs
- Receiver fills in from mempool, only requests missing transactions
- Reduces bandwidth by ~90% during normal operation
### 3.3 DNS Seed Infrastructure (Easy)
**Problem:** `dnsseed=0` when Tor-only means no automatic peer discovery.
**Fix:** Run a DNS seed server that resolves to known reliable onion addresses:
```
seed.cryptographic-triangles.org → returns onion addresses of healthy nodes
```
---
## Priority 4: User Experience (Medium Impact)
### 4.1 Progress Reporting for IBD (Easy)
**Problem:** Users see "downloading blocks..." with no useful progress indicator.
**Fix:**
- Report `headers` vs `blocks` progress separately
- Show estimated time remaining based on download speed
- Log progress every 1000 blocks (currently every 5000)
- Qt wallet: update progress bar more frequently
### 4.2 Staking Dashboard Improvements (Easy)
**Problem:** Qt wallet shows staking info but not clearly.
**Fix:**
- Show expected time to stake more prominently
- Display staking weight as percentage of network
- Notify when stake is found (system notification)
- Show "staking" indicator in system tray
### 4.3 Transaction Fee Estimation (Medium Effort)
**Problem:** No fee estimation. Users guess.
**Fix:** Track recent block inclusion rates by fee level, provide fee recommendations.
---
## Priority 5: Code Modernization (Low Urgency, Good Hygiene)
### 5.1 C++17/20 Features
- Replace raw pointers with smart pointers where safe
- Use `std::optional`, `std::string_view`, `std::filesystem`
- Replace boost::filesystem with std::filesystem (C++17)
### 5.2 Build System
- CMake is already in place (good)
- Add sanitizers (ASAN, UBSAN) to CI
- Static analysis with clang-tidy
### 5.3 Testing
- Current test coverage is thin
- Add unit tests for:
- Checkpoint validation
- Stake modifier computation
- Bootstrap download/resume
- Wallet BDB recovery
- Orphan block handling
---
## What NOT to Change
These are consensus-critical and must remain identical:
- Block validation rules
- Stake modifier computation (`ComputeNextStakeModifier`)
- Transaction signature verification
- Block reward schedule
- PoW/PoS target computation
- Chain trust / difficulty adjustment
- Message serialization format
- Protocol version handshaking
Any change to these requires a coordinated network upgrade (hard fork).
---
## Implementation Order
1. **This week:** Update checkpoints (1.1), increase orphan limit (1.2), wallet backup before save (2.1)
2. **Next week:** Better progress reporting (4.1), increase batch size (1.3)
3. **Month 1:** Wallet versioning (2.3), bootstrap resume (1.5)
4. **Month 2:** Header-first sync (1.4), peer reliability (3.1)
5. **Month 3+:** HD wallet (2.4), compact blocks (3.2)
+7 -5
View File
@@ -27,12 +27,14 @@ Triangles is a privacy-focused cryptocurrency featuring Proof-of-Stake consensus
The Triangles network operates exclusively over Tor for privacy:
**Tor v3 Seeds:**
- `gxvrhv3qitnc6kobrhsrse46bmcfitnybapor3or3oczzuxn6hfzxyid.onion:24112`
- `futmtrvh6j34t7s6yjdxfia6iwuyfzwh4k5eqfof5kfhoqk3xmi3qoqd.onion:24112`
- `jbpfhe7zw3qm67wy3j2ayysp3mnrjobopthnko3b3sgahqtecblwqmid.onion:24112`
- `uddaxjbo3lh2zskg7w6gwln4ty5cel7q4c5jbx7fdtv6zf2j47gdlyad.onion:24112`
- `el5sirhhleecuctpeeprelzubpqmoqivvra3rzlwbjttinxa4fq3wnid.onion:24112`
- `sj5dhybnlp3v4y5niyc5unrnd6s43lyx5ibup7rolyosjbi2u2hsbvyd.onion:24112`
- `i3kr5meha7se4ns3wss3h7v46m6uksfzv4wrohdqxpj6n35wyo2bvlid.onion:24112`
**DNS Seeds:**
- `seed1.cryptographic-triangles.org`
- `seed2.cryptographic-triangles.org`
**HTTP Seed List:**
- `seeds.cryptographic-triangles.org/seeds.txt` - Dynamically updated list of active onion peers
## Building from Source
+165 -42
View File
@@ -22,8 +22,10 @@
#ifdef WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#endif
@@ -68,52 +70,170 @@ static bool RecvUntil(SOCKET sock, std::string& out, const std::string& delim)
}
}
// Direct TCP connection bypassing Tor SOCKS proxy.
// Used for bootstrap downloads where the server is on clearnet.
static SOCKET ConnectDirectTCP(const std::string& host, int port, std::string& strError)
{
struct addrinfo hints, *result, *rp;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
std::string portStr = std::to_string(port);
int rc = getaddrinfo(host.c_str(), portStr.c_str(), &hints, &result);
if (rc != 0) {
strError = "DNS resolution failed for " + host;
return INVALID_SOCKET;
}
SOCKET hSocket = INVALID_SOCKET;
for (rp = result; rp != NULL; rp = rp->ai_next) {
hSocket = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (hSocket == INVALID_SOCKET)
continue;
if (connect(hSocket, rp->ai_addr, (int)rp->ai_addrlen) == 0)
break; // success
closesocket(hSocket);
hSocket = INVALID_SOCKET;
}
freeaddrinfo(result);
if (hSocket == INVALID_SOCKET)
strError = "Cannot connect to " + host + ":" + portStr;
return hSocket;
}
bool DownloadFile(const std::string& host, const std::string& urlPath,
const fs::path& destPath,
ProgressCallback progressFn,
std::string& strError)
std::string& strError,
bool noProxy)
{
try {
// Connect through Tor SOCKS proxy (ConnectSocketByName respects SetProxy)
std::string currentHost = host;
std::string currentPath = urlPath;
SOCKET hSocket = INVALID_SOCKET;
CService addr;
if (!ConnectSocketByName(addr, hSocket, host.c_str(), PORT, 30)) {
strError = "Cannot connect to " + host + " (check Tor proxy)";
return false;
}
// 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";
if (!SendAll(hSocket, request.data(), request.size())) {
closesocket(hSocket);
strError = "Failed to send request to " + host;
return false;
}
// Read response headers
std::string headerData;
if (!RecvUntil(hSocket, headerData, "\r\n\r\n")) {
closesocket(hSocket);
strError = "Failed to read HTTP headers from " + host;
return false;
}
int redirectCount = 0;
const int MAX_REDIRECTS = 5;
// Parse status code from "HTTP/1.x NNN ..."
unsigned int status_code = 0;
size_t sp = headerData.find(' ');
if (sp != std::string::npos)
status_code = atoi(headerData.c_str() + sp + 1);
// Connection + redirect loop
while (true) {
if (noProxy) {
hSocket = ConnectDirectTCP(currentHost, PORT, strError);
if (hSocket == INVALID_SOCKET)
return false;
} else {
CService addr;
if (!ConnectSocketByName(addr, hSocket, currentHost.c_str(), PORT, 30)) {
strError = "Cannot connect to " + currentHost + " (check Tor proxy)";
return false;
}
}
if (status_code != 200) {
closesocket(hSocket);
strError = "HTTP error " + std::to_string(status_code) + " for " + urlPath;
return false;
// Send HTTP GET request
std::string request =
"GET " + currentPath + " HTTP/1.1\r\n"
"Host: " + currentHost + "\r\n"
"Connection: close\r\n"
"User-Agent: Triangles\r\n"
"\r\n";
if (!SendAll(hSocket, request.data(), request.size())) {
closesocket(hSocket);
strError = "Failed to send request to " + currentHost;
return false;
}
// Read response headers
if (!RecvUntil(hSocket, headerData, "\r\n\r\n")) {
closesocket(hSocket);
strError = "Failed to read HTTP headers from " + currentHost;
return false;
}
// Parse status code from "HTTP/1.x NNN ..."
unsigned int status_code = 0;
size_t sp = headerData.find(' ');
if (sp != std::string::npos)
status_code = atoi(headerData.c_str() + sp + 1);
// Handle HTTP redirects
if (status_code == 301 || status_code == 302 ||
status_code == 307 || status_code == 308) {
closesocket(hSocket);
hSocket = INVALID_SOCKET;
if (++redirectCount > MAX_REDIRECTS) {
strError = "Too many redirects for " + urlPath;
return false;
}
// Find Location header (case-insensitive)
std::string lowerHdr = headerData;
std::transform(lowerHdr.begin(), lowerHdr.end(),
lowerHdr.begin(), ::tolower);
size_t locPos = lowerHdr.find("\nlocation:");
if (locPos == std::string::npos) {
strError = "Redirect " + std::to_string(status_code) + " without Location header";
return false;
}
size_t valStart = locPos + 10; // skip "\nlocation:"
while (valStart < headerData.size() && headerData[valStart] == ' ')
valStart++;
size_t lineEnd = headerData.find("\r\n", valStart);
std::string location;
if (lineEnd != std::string::npos)
location = headerData.substr(valStart, lineEnd - valStart);
else
location = headerData.substr(valStart);
boost::trim(location);
// Reject HTTPS redirects (no TLS support)
if (location.compare(0, 8, "https://") == 0) {
strError = "Server redirected to HTTPS (not supported). "
"Configure bootstrap server for plain HTTP.";
return false;
}
// Parse redirect URL
if (location.compare(0, 7, "http://") == 0) {
std::string rest = location.substr(7);
size_t pathStart = rest.find('/');
if (pathStart != std::string::npos) {
currentHost = rest.substr(0, pathStart);
currentPath = rest.substr(pathStart);
} else {
currentHost = rest;
currentPath = "/";
}
// Strip port from host if present
size_t colonPos = currentHost.find(':');
if (colonPos != std::string::npos)
currentHost = currentHost.substr(0, colonPos);
} else if (!location.empty() && location[0] == '/') {
currentPath = location;
} else {
strError = "Unsupported redirect location: " + location;
return false;
}
printf("Bootstrap: redirect %d -> %s%s\n",
status_code, currentHost.c_str(), currentPath.c_str());
continue;
}
if (status_code != 200) {
closesocket(hSocket);
strError = "HTTP error " + std::to_string(status_code) + " for " + currentPath;
return false;
}
break; // Got 200, proceed to download
}
// Parse Content-Length
@@ -183,13 +303,14 @@ bool DownloadFile(const std::string& host, const std::string& urlPath,
bool FetchFileList(const std::string& host,
std::vector<std::string>& files,
std::string& strError)
std::string& strError,
bool noProxy)
{
// 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))
if (!DownloadFile(host, urlPath, tmpPath, nullptr, strError, noProxy))
return false;
// Read lines
@@ -458,10 +579,12 @@ bool DownloadBootstrap(const std::string& host,
bool gotBlockFile = false;
// Try downloading bootstrap.tar.gz first
// Bootstrap server is on clearnet — bypass Tor proxy for DNS + HTTP
const bool noProxy = true;
fs::path tmpTarGz = dataDir / "bootstrap.tar.gz.tmp";
std::string tarUrl = std::string(BASE_PATH) + "triangles-bootstrap.tar.gz";
bool tarDownloaded = DownloadFile(host, tarUrl, tmpTarGz, progressFn, strError);
bool tarDownloaded = DownloadFile(host, tarUrl, tmpTarGz, progressFn, strError, noProxy);
if (tarDownloaded) {
bool extractOk = ExtractTarGz(tmpTarGz, dataDir, strError);
@@ -476,7 +599,7 @@ bool DownloadBootstrap(const std::string& host,
// Fallback: try filelist.txt + individual file downloads
std::string fallbackError;
std::vector<std::string> files;
if (!FetchFileList(host, files, fallbackError)) {
if (!FetchFileList(host, files, fallbackError, noProxy)) {
if (!tarDownloaded)
strError = strError + " (fallback also failed: " + fallbackError + ")";
else
@@ -489,7 +612,7 @@ bool DownloadBootstrap(const std::string& host,
fs::create_directories(destPath.parent_path());
std::string urlPath = std::string(BASE_PATH) + files[i];
if (!DownloadFile(host, urlPath, destPath, progressFn, strError))
if (!DownloadFile(host, urlPath, destPath, progressFn, strError, noProxy))
return false;
}
+7 -3
View File
@@ -22,16 +22,20 @@ namespace Bootstrap {
// 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
// Download a single file via HTTP GET, write to destPath.
// If noProxy is true, bypass Tor SOCKS proxy and connect directly
// (used for clearnet bootstrap downloads).
bool DownloadFile(const std::string& host, const std::string& urlPath,
const boost::filesystem::path& destPath,
ProgressCallback progressFn,
std::string& strError);
std::string& strError,
bool noProxy = false);
// Fetch the file manifest (list of relative paths to download)
bool FetchFileList(const std::string& host,
std::vector<std::string>& files,
std::string& strError);
std::string& strError,
bool noProxy = false);
// Download bootstrap.tar.gz and extract to dataDir.
// Falls back to filelist.txt + individual file download if tar.gz unavailable.
+6
View File
@@ -33,6 +33,9 @@ namespace Checkpoints
{ 10881, uint256("0x4b6554c45e1e6764a6f3c309c47baf53c9edd81f624e52b072518cd15da237e6")},
{ 17650, uint256("0x224940e1f986a202209b8e762728d1452ab45870c308abf84905674acf326a47")},
{2186940, uint256("0xbd952e8d4a612e336d840ad924a7e09395e36bcd9d929b302e47e60b5c3098c0")},
{2190000, uint256("0x682baf783581468ba18f9967254a7f3944e8b8c4cc7101e7d99b68f4f9dd5271")},
{2200000, uint256("0x0a8d0442f031f1258120f713f34e45f4f9a625fb753558e27b89b32ad5a9a740")},
{2205000, uint256("0xf7aa893ec012181e321783d6a5487addf6997377908faa3e760c9054a4217d29")},
};
static MapCheckpoints mapCheckpointsTestnet = {
@@ -49,6 +52,9 @@ namespace Checkpoints
{ 10881, uint256("0x4b6554c45e1e6764a6f3c309c47baf53c9edd81f624e52b072518cd15da237e6")},
{ 17650, uint256("0x224940e1f986a202209b8e762728d1452ab45870c308abf84905674acf326a47")},
{2186940, uint256("0xbd952e8d4a612e336d840ad924a7e09395e36bcd9d929b302e47e60b5c3098c0")},
{2190000, uint256("0x682baf783581468ba18f9967254a7f3944e8b8c4cc7101e7d99b68f4f9dd5271")},
{2200000, uint256("0x0a8d0442f031f1258120f713f34e45f4f9a625fb753558e27b89b32ad5a9a740")},
{2205000, uint256("0xf7aa893ec012181e321783d6a5487addf6997377908faa3e760c9054a4217d29")},
};
bool CheckHardened(int nHeight, const uint256& hash)
+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 8
#define CLIENT_VERSION_REVISION 1
#define CLIENT_VERSION_REVISION 3
#define CLIENT_VERSION_BUILD 0
// Converts the parameter X to a string after macro replacement on X has been performed.
+1
View File
@@ -28,6 +28,7 @@ extern unsigned int nWalletDBUpdated;
void ThreadFlushWalletDB(void* parg);
bool BackupWallet(const CWallet& wallet, const std::string& strDest);
bool AutoBackupWallet(const boost::filesystem::path& walletPath);
class CDBEnv
+27
View File
@@ -952,6 +952,18 @@ bool AppInit2()
return false;
}
// Handle -reindex: delete the LevelDB block index so it gets rebuilt
// from the raw blk*.dat files via FastImportBlockFile().
// This recalculates money supply, tx index, and UTXO set from scratch.
if (GetBoolArg("-reindex", false))
{
printf("Reindex requested: removing block index database...\n");
uiInterface.InitMessage(_("Removing block index for reindex..."));
fs::path txleveldbPath = GetDataDir() / "txleveldb";
if (fs::exists(txleveldbPath))
fs::remove_all(txleveldbPath);
}
uiInterface.InitMessage(_("Loading block index..."));
printf("Loading block index...\n");
nStart = GetTimeMillis();
@@ -1041,6 +1053,21 @@ bool AppInit2()
nStart = GetTimeMillis();
bool fFirstRun = true;
pwalletMain = new CWallet(strWalletFileName);
// Auto-backup wallet.dat before loading (protects against corruption during load/flush)
{
fs::path walletPath = GetDataDir() / strWalletFileName;
if (fs::exists(walletPath)) {
uintmax_t wsize = fs::file_size(walletPath);
printf("Wallet file size: %llu bytes\n", (unsigned long long)wsize);
if (wsize < 1024) {
strErrors << _("WARNING: wallet.dat is suspiciously small (") << wsize << _(" bytes). It may be corrupt.\n");
printf("WARNING: wallet.dat is only %llu bytes - possibly corrupt!\n", (unsigned long long)wsize);
}
AutoBackupWallet(walletPath);
}
}
DBErrors nLoadWalletRet = pwalletMain->LoadWallet(fFirstRun);
if (nLoadWalletRet != DB_LOAD_OK)
{
+208 -45
View File
@@ -116,8 +116,10 @@ static CCriticalSection cs_PostIbdWork;
static bool fPostIbdWorkStarted = false;
static const unsigned int MAX_HEADER_SYNC_CACHE = 50000;
static const unsigned int HEADER_DOWNLOAD_WINDOW = 128;
static const unsigned int HEADER_DOWNLOAD_WINDOW = 512; // Increased from 128 for parallel downloads
static const unsigned int HEADER_DOWNLOAD_PER_PEER = 64; // Max blocks to request from each peer
static const int64_t HEADER_REQUEST_TIMEOUT_MICROS = 30 * 1000000;
static const int64_t HEADER_REDUNDANT_REQUEST_MICROS = 10 * 1000000; // Request from another peer after 10s
static void ThreadPostIbdWork(void* parg)
{
@@ -405,6 +407,89 @@ static void ContinueHeaderSync(CNode* pfrom, const uint256& hashTip)
pfrom->PushMessage("getheaders", locator, uint256(0));
}
// Parallel block downloading: distribute blocks across all available peers
static unsigned int QueueHeaderSyncBlocksParallel(unsigned int nWindow)
{
if (hashBestHeaderSync == 0)
return 0;
const std::vector<uint256> vPath = GetHeaderSyncDownloadPath(hashBestHeaderSync);
if (vPath.empty())
return 0;
// Collect eligible peers
std::vector<CNode*> vEligiblePeers;
{
LOCK(cs_vNodes);
for (CNode* pnode : vNodes)
{
if (!pnode->fClient && pnode->nVersion != 0 && !pnode->fDisconnect)
vEligiblePeers.push_back(pnode);
}
}
if (vEligiblePeers.empty())
return 0;
const int64_t nNow = GetTime() * 1000000;
unsigned int nInFlight = CountHeaderSyncInFlight();
unsigned int nQueued = 0;
unsigned int nPeerIndex = 0;
// Distribute blocks across peers in round-robin fashion
for (std::vector<uint256>::const_iterator it = vPath.begin(); it != vPath.end(); ++it)
{
if (nInFlight + nQueued >= nWindow)
break;
std::map<uint256, CHeaderSyncNode>::iterator mi = mapHeaderSync.find(*it);
if (mi == mapHeaderSync.end())
continue;
// Check if already requested recently
bool fNeedsRequest = false;
if (!mi->second.fRequested)
{
// Never requested - request now
fNeedsRequest = true;
}
else if (nNow - mi->second.nLastRequestTime >= HEADER_REQUEST_TIMEOUT_MICROS)
{
// Timeout expired - retry
fNeedsRequest = true;
}
else if (nNow - mi->second.nLastRequestTime >= HEADER_REDUNDANT_REQUEST_MICROS)
{
// Redundant request: ask another peer if original is slow
// This creates parallel downloads for slow blocks
fNeedsRequest = true;
}
if (!fNeedsRequest)
continue;
// Round-robin across peers to distribute load
CNode* pnode = vEligiblePeers[nPeerIndex % vEligiblePeers.size()];
pnode->AskFor(CInv(MSG_BLOCK, *it));
// Update tracking (only on first request, not redundant)
if (!mi->second.fRequested || nNow - mi->second.nLastRequestTime >= HEADER_REQUEST_TIMEOUT_MICROS)
{
mi->second.fRequested = true;
mi->second.nLastRequestTime = nNow;
}
++nQueued;
++nPeerIndex;
}
if (nQueued > 0)
printf("IBD-DIAG: parallel queue distributed %u blocks across %zu peers (window=%u, inflight=%u)\n",
nQueued, vEligiblePeers.size(), nWindow, nInFlight);
return nQueued;
}
} // namespace
//////////////////////////////////////////////////////////////////////////////
@@ -1322,6 +1407,48 @@ uint256 WantedByOrphan(const CBlock* pblockOrphan)
return pblockOrphan->hashPrevBlock;
}
// Evict excess orphan blocks when limit is exceeded
// Returns number of orphans evicted
unsigned int LimitOrphanBlocks(unsigned int nMaxOrphans)
{
unsigned int nEvicted = 0;
while (mapOrphanBlocks.size() > nMaxOrphans)
{
// Evict a random orphan
uint256 randomhash = GetRandHash();
auto it = mapOrphanBlocks.lower_bound(randomhash);
if (it == mapOrphanBlocks.end())
it = mapOrphanBlocks.begin();
if (it == mapOrphanBlocks.end())
break; // No orphans to evict
CBlock* pblockEvict = it->second;
uint256 evictHash = it->first;
// Remove from by-prev index
for (auto range = mapOrphanBlocksByPrev.equal_range(pblockEvict->hashPrevBlock);
range.first != range.second; ++range.first)
{
if (range.first->second == pblockEvict) {
mapOrphanBlocksByPrev.erase(range.first);
break;
}
}
setStakeSeenOrphan.erase(pblockEvict->GetProofOfStake());
delete pblockEvict;
mapOrphanBlocks.erase(evictHash);
nEvicted++;
}
if (nEvicted > 0)
printf("LimitOrphanBlocks: evicted %u orphan(s), %u remain\n",
nEvicted, (unsigned int)mapOrphanBlocks.size());
return nEvicted;
}
// miner's coin base reward
int64_t GetProofOfWorkReward(int64_t nFees)
{
@@ -1415,9 +1542,13 @@ static unsigned int GetNextTargetRequired_(const CBlockIndex* pindexLast, bool f
return bnTargetLimit.GetCompact(); // genesis block
const CBlockIndex* pindexPrev = GetLastBlockIndex(pindexLast, fProofOfStake);
if (pindexPrev == NULL)
return bnTargetLimit.GetCompact(); // no previous block of this type
if (pindexPrev->pprev == NULL)
return bnTargetLimit.GetCompact(); // first block
const CBlockIndex* pindexPrevPrev = GetLastBlockIndex(pindexPrev->pprev, fProofOfStake);
if (pindexPrevPrev == NULL)
return bnTargetLimit.GetCompact(); // no second previous block of this type
if (pindexPrevPrev->pprev == NULL)
return bnTargetLimit.GetCompact(); // second block
@@ -2324,7 +2455,15 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck)
bool static Reorganize(CTxDB& txdb, CBlockIndex* pindexNew)
{
printf("REORGANIZE\n");
printf("REORGANIZE: Switching chains\n");
printf(" Old tip: %s height %d trust %s\n",
pindexBest->GetBlockHash().ToString().substr(0,20).c_str(),
pindexBest->nHeight,
CBigNum(pindexBest->nChainTrust).ToString().c_str());
printf(" New tip: %s height %d trust %s\n",
pindexNew->GetBlockHash().ToString().substr(0,20).c_str(),
pindexNew->nHeight,
CBigNum(pindexNew->nChainTrust).ToString().c_str());
// Find the fork
CBlockIndex* pfork = pindexBest;
@@ -2351,8 +2490,17 @@ bool static Reorganize(CTxDB& txdb, CBlockIndex* pindexNew)
vConnect.push_back(pindex);
reverse(vConnect.begin(), vConnect.end());
printf("REORGANIZE: Disconnect %" PRIszu " blocks; %s..%s\n", vDisconnect.size(), pfork->GetBlockHash().ToString().substr(0,20).c_str(), pindexBest->GetBlockHash().ToString().substr(0,20).c_str());
printf("REORGANIZE: Connect %" PRIszu " blocks; %s..%s\n", vConnect.size(), pfork->GetBlockHash().ToString().substr(0,20).c_str(), pindexNew->GetBlockHash().ToString().substr(0,20).c_str());
printf("REORGANIZE: Fork point at height %d: %s\n",
pfork->nHeight,
pfork->GetBlockHash().ToString().substr(0,20).c_str());
printf("REORGANIZE: Disconnect %" PRIszu " blocks (heights %d..%d)\n",
vDisconnect.size(),
pfork->nHeight + 1,
pindexBest->nHeight);
printf("REORGANIZE: Connect %" PRIszu " blocks (heights %d..%d)\n",
vConnect.size(),
pfork->nHeight + 1,
pindexNew->nHeight);
// Disconnect shorter branch
vector<CTransaction> vResurrect;
@@ -2370,12 +2518,6 @@ bool static Reorganize(CTxDB& txdb, CBlockIndex* pindexNew)
vResurrect.push_back(tx);
}
// Remove disconnected PoS blocks from setStakeSeen so they don't
// block acceptance of valid blocks on the winning chain.
for (CBlockIndex* pindex : vDisconnect)
if (pindex->IsProofOfStake())
setStakeSeen.erase(make_pair(pindex->prevoutStake, pindex->nStakeTime));
// Connect longer branch
vector<CTransaction> vDelete;
for (unsigned int i = 0; i < vConnect.size(); i++)
@@ -2403,19 +2545,37 @@ bool static Reorganize(CTxDB& txdb, CBlockIndex* pindexNew)
if (!txdb.TxnCommit())
return error("Reorganize() : TxnCommit failed");
// Disconnect shorter branch
// ======================================================================
// CRITICAL: All operations below this point must be in-memory only and
// should never fail. The DB transaction is committed, so we cannot abort.
// ======================================================================
// Disconnect shorter branch (in-memory only)
for (CBlockIndex* pindex : vDisconnect)
if (pindex->pprev)
pindex->pprev->pnext = NULL;
// Connect longer branch
// Connect longer branch (in-memory only)
for (CBlockIndex* pindex : vConnect)
if (pindex->pprev)
pindex->pprev->pnext = pindex;
// Remove disconnected PoS blocks from setStakeSeen so they don't
// block acceptance of valid blocks on the winning chain.
// This MUST happen after commit to maintain consistency.
for (CBlockIndex* pindex : vDisconnect)
if (pindex->IsProofOfStake())
setStakeSeen.erase(make_pair(pindex->prevoutStake, pindex->nStakeTime));
// Resurrect memory transactions that were in the disconnected branch
unsigned int nResurrected = 0;
for (CTransaction& tx : vResurrect)
tx.AcceptToMemoryPool(txdb, false);
{
if (tx.AcceptToMemoryPool(txdb, false))
nResurrected++;
}
if (nResurrected > 0)
printf("REORGANIZE: resurrected %u transactions to mempool\n", nResurrected);
// Delete redundant memory transactions that are in the connected branch
for (CTransaction& tx : vDelete) {
@@ -2423,7 +2583,8 @@ bool static Reorganize(CTxDB& txdb, CBlockIndex* pindexNew)
mempool.removeConflicts(tx);
}
printf("REORGANIZE: done\n");
printf("REORGANIZE: done (fork at height %d, %zu disconnected, %zu connected)\n",
pfork->nHeight, vDisconnect.size(), vConnect.size());
return true;
}
@@ -2613,6 +2774,9 @@ bool CBlock::SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew)
{
printf("*** Initial block download complete at height %d ***\n", nBestHeight);
// Trim orphan blocks to normal limit now that IBD is done
LimitOrphanBlocks(MAX_ORPHAN_BLOCKS);
// Update wallet best chain locator now that IBD is done
const CBlockLocator locator(pindexBest);
::SetBestChain(locator);
@@ -2806,15 +2970,15 @@ bool CBlock::AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos, const u
// 2. Equal trust: deterministic hash tiebreaker — lower tip hash wins.
// This ensures all nodes converge on the same chain even when two
// forks have identical cumulative difficulty (common in PoS).
// Rate-limited to one equal-trust reorg per 10 minutes to prevent
// oscillation from Tor-latency-induced competing announcements.
// Rate-limited to one equal-trust reorg per 2 minutes — short enough
// for fast convergence but long enough to prevent oscillation on Tor.
bool fNewBest = false;
static int64_t nLastEqualTrustReorg = 0;
if (pindexNew->nChainTrust > nBestChainTrust)
fNewBest = true;
else if (pindexNew->nChainTrust == nBestChainTrust && pindexBest &&
pindexNew->GetBlockHash() < pindexBest->GetBlockHash() &&
GetTime() - nLastEqualTrustReorg > 10 * 60)
GetTime() - nLastEqualTrustReorg > 2 * 60)
{
fNewBest = true;
nLastEqualTrustReorg = GetTime();
@@ -3026,14 +3190,20 @@ bool CBlock::AcceptBlock()
if (!AddToBlockIndex(nFile, nBlockPos, hashProofOfStake))
return error("AcceptBlock() : AddToBlockIndex failed");
// Relay inventory, but don't relay old inventory during initial block download
// Push new tip block directly to all peers. On a small Tor-only
// network the inv→getdata→block round-trip adds 1-2 seconds of latency
// per hop — enough for a competing staker to create a fork. Pushing
// the full block immediately cuts propagation to a single hop.
int nBlockEstimate = Checkpoints::GetTotalBlocksEstimate();
if (hashBestChain == hash)
{
LOCK(cs_vNodes);
for (CNode* pnode : vNodes)
if (nBestHeight > (pnode->nStartingHeight != -1 ? pnode->nStartingHeight - 2000 : nBlockEstimate))
pnode->PushInventory(CInv(MSG_BLOCK, hash));
{
pnode->PushMessage("block", *this);
pnode->AddInventoryKnown(CInv(MSG_BLOCK, hash));
}
}
return true;
@@ -3144,29 +3314,7 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock)
// Allow more orphans during IBD so out-of-order blocks from parallel
// downloads don't get evicted and re-requested.
unsigned int nMaxOrphans = IsInitialBlockDownload() ? MAX_ORPHAN_BLOCKS_IBD : MAX_ORPHAN_BLOCKS;
if (mapOrphanBlocks.size() > nMaxOrphans)
{
// Evict a random orphan
uint256 randomhash = GetRandHash();
auto it = mapOrphanBlocks.lower_bound(randomhash);
if (it == mapOrphanBlocks.end())
it = mapOrphanBlocks.begin();
CBlock* pblockEvict = it->second;
uint256 evictHash = it->first;
// Remove from by-prev index
for (auto range = mapOrphanBlocksByPrev.equal_range(pblockEvict->hashPrevBlock);
range.first != range.second; ++range.first)
{
if (range.first->second == pblockEvict) {
mapOrphanBlocksByPrev.erase(range.first);
break;
}
}
setStakeSeenOrphan.erase(pblockEvict->GetProofOfStake());
delete pblockEvict;
mapOrphanBlocks.erase(evictHash);
printf("ProcessBlock: orphan eviction, %u orphans remain\n", (unsigned int)mapOrphanBlocks.size());
}
LimitOrphanBlocks(nMaxOrphans);
// Ask this guy to fill in what we're missing
if (pfrom && pindexBest)
@@ -3212,9 +3360,10 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock)
if (nBestHeight % 5000 == 0 || !IsInitialBlockDownload())
printf("ProcessBlock: ACCEPTED block %d\n", nBestHeight);
if (pfrom && hashBestHeaderSync != 0)
if (hashBestHeaderSync != 0)
{
const unsigned int nQueued = QueueHeaderSyncBlocks(pfrom, HEADER_DOWNLOAD_WINDOW);
// Use parallel queue to distribute across all peers
const unsigned int nQueued = QueueHeaderSyncBlocksParallel(HEADER_DOWNLOAD_WINDOW);
if (nQueued > 0)
printf("IBD-DIAG: queued %u more blocks from header planner after accepting %s\n",
nQueued, hash.ToString().substr(0,20).c_str());
@@ -4523,7 +4672,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
int nRequested = 0;
if (hashBestHeaderSync != 0)
nRequested = QueueHeaderSyncBlocks(pfrom, HEADER_DOWNLOAD_WINDOW);
nRequested = QueueHeaderSyncBlocksParallel(HEADER_DOWNLOAD_WINDOW);
if (nNewHeaders > 0 || nRequested > 0)
printf("IBD-DIAG: accepted %d new headers, queued %d blocks from %zu headers (peer=%s bestHeader=%s)\n",
@@ -5165,6 +5314,20 @@ bool SendMessages(CNode* pto, bool fSendTrickle)
pto->PushMessage("inv", vInv);
//
// Periodic chain-tip sync: every 45 seconds, ask each peer if they
// have blocks we don't. On a small Tor-only network, transient
// partitions can cause forks that persist silently — this ensures
// nodes discover the longer chain even without explicit announcement.
//
if (!IsInitialBlockDownload() && !pto->fClient && pindexBest &&
GetTime() - pto->nLastTipCheck > 45)
{
pto->nLastTipCheck = GetTime();
pto->pindexLastGetBlocksBegin = NULL; // reset dedup to force request
pto->PushGetBlocks(pindexBest, uint256(0));
}
//
// Stall detection: if we're still catching up and no new blocks for
// a while, re-request. Active during IBD (5s timeout) and also
+4 -3
View File
@@ -37,7 +37,7 @@ static const unsigned int MAX_BLOCK_SIZE = 1000000;
static const unsigned int MAX_BLOCK_SIZE_GEN = MAX_BLOCK_SIZE/2;
static const unsigned int MAX_BLOCK_SIGOPS = MAX_BLOCK_SIZE/50;
static const unsigned int MAX_ORPHAN_TRANSACTIONS = MAX_BLOCK_SIZE/100;
static const unsigned int MAX_ORPHAN_BLOCKS = 750;
static const unsigned int MAX_ORPHAN_BLOCKS = 2000;
static const unsigned int MAX_ORPHAN_BLOCKS_IBD = 4000;
static const unsigned int MAX_INV_SZ = 50000;
static const int64_t MIN_TX_FEE = (1 * CENT) / 100;
@@ -59,10 +59,10 @@ static const int fHaveUPnP = false;
static const uint256 hashGenesisBlockOfficial("0x7e7a6e4dd5fe895106fca912dfbacaeaf2a89e76c6a588df8ff96e0e18b96021");
static const uint256 hashGenesisBlockTestNet ("0x7e7a6e4dd5fe895106fca912dfbacaeaf2a89e76c6a588df8ff96e0e18b96021");
inline int64_t GetMaxTimeDrift(int nHeight) { return (nHeight >= FORK_HEIGHT_V5_4) ? 3 * 60 : 10 * 60; }
inline int64_t GetMaxTimeDrift(int nHeight) { return (nHeight >= FORK_HEIGHT_V5_4) ? 90 : 10 * 60; }
inline int64_t PastDrift(int64_t nTime, int nHeight) { return nTime - GetMaxTimeDrift(nHeight); }
inline int64_t FutureDrift(int64_t nTime, int nHeight) { return nTime + GetMaxTimeDrift(nHeight); }
// Height-less overloads always use post-V5.4 rules (3-min drift).
// Height-less overloads always use post-V5.4 rules (90-second drift).
// All nodes are well past FORK_HEIGHT_V5_4; using the global nBestHeight
// here previously caused nodes at different heights to disagree on block
// validity during the fork transition — a consensus-splitting bug.
@@ -136,6 +136,7 @@ bool IsInitialBlockDownload();
std::string GetWarnings(std::string strFor);
bool GetTransaction(const uint256 &hash, CTransaction &tx, uint256 &hashBlock);
uint256 WantedByOrphan(const CBlock* pblockOrphan);
unsigned int LimitOrphanBlocks(unsigned int nMaxOrphans);
const CBlockIndex* GetLastBlockIndex(const CBlockIndex* pindex, bool fProofOfStake);
void StakeMiner(CWallet *pwallet);
void ResendWalletTransactions(bool fForce = false);
+1 -1
View File
@@ -413,7 +413,7 @@ void StakeMiner(CWallet *pwallet)
if (fTryToSync)
{
fTryToSync = false;
if (vNodes.size() < 1 || nBestHeight < GetNumBlocksOfPeers())
if (vNodes.size() < 2 || nBestHeight < GetNumBlocksOfPeers())
{
MilliSleep(60000);
continue;
+2
View File
@@ -272,6 +272,7 @@ public:
CBlockIndex* pindexLastGetHeadersBegin;
uint256 hashLastGetHeadersEnd;
int nStartingHeight;
int64_t nLastTipCheck; // last time we asked this peer for chain tip
// flood relay
std::vector<CAddress> vAddrToSend;
@@ -319,6 +320,7 @@ public:
pindexLastGetHeadersBegin = 0;
hashLastGetHeadersEnd = 0;
nStartingHeight = -1;
nLastTipCheck = 0;
fGetAddr = false;
nMisbehavior = 0;
hashCheckpointKnown = 0;
+46
View File
@@ -28,6 +28,9 @@ double GetDifficulty(const CBlockIndex* blockindex)
blockindex = GetLastBlockIndex(pindexBest, false);
}
if (blockindex == NULL)
return 1.0;
int nShift = (blockindex->nBits >> 24) & 0xff;
double dDiff =
@@ -360,6 +363,49 @@ Value gettxoutsetinfo(const Array& params, bool fHelp)
return obj;
}
Value recalculatesupply(const Array& params, bool fHelp)
{
if (fHelp || params.size() != 0)
throw runtime_error(
"recalculatesupply\n"
"Recalculates the money supply by summing all unspent transaction outputs.\n"
"Updates the stored money supply value at the chain tip and persists it to disk.\n"
"Returns the old and new supply values for comparison.\n"
"\nWARNING: This modifies blockchain index state. Only use if money supply is incorrect.");
if (!pindexBest)
throw runtime_error("recalculatesupply: no best block");
int nUtxoCount = 0;
CTxDB txdb;
int64_t nCalculatedSupply = txdb.SumUtxoValues(nUtxoCount);
int64_t nOldSupply = pindexBest->nMoneySupply;
int64_t nDifference = nCalculatedSupply - nOldSupply;
// Sanity check: difference should be reasonable (not millions of TRI)
// Max supply is 2,222,222 TRI, so any difference > 1M TRI is suspicious
if (abs64(nDifference) > 1000000 * COIN)
throw runtime_error(strprintf(
"recalculatesupply: calculated supply differs by %s TRI - this is abnormal, refusing to update",
FormatMoney(abs64(nDifference)).c_str()));
// Update the chain tip's money supply
pindexBest->nMoneySupply = nCalculatedSupply;
// Persist to LevelDB
CTxDB txdbWrite;
if (!txdbWrite.WriteBlockIndex(CDiskBlockIndex(pindexBest)))
throw runtime_error("recalculatesupply: failed to write updated block index");
Object result;
result.push_back(Pair("height", (int)nBestHeight));
result.push_back(Pair("old_supply", ValueFromAmount(nOldSupply)));
result.push_back(Pair("new_supply", ValueFromAmount(nCalculatedSupply)));
result.push_back(Pair("difference", ValueFromAmount(nDifference)));
result.push_back(Pair("utxo_count", nUtxoCount));
return result;
}
// triangles: get information of sync-checkpoint
Value getcheckpoint(const Array& params, bool fHelp)
{
+32 -1
View File
@@ -78,7 +78,8 @@ Value getstakinginfo(const Array& params, bool fHelp)
if (fHelp || params.size() != 0)
throw runtime_error(
"getstakinginfo\n"
"Returns an object containing staking-related information.");
"Returns an object containing staking-related information.\n"
"Includes diagnostic details about why staking may be disabled.");
uint64_t nMinWeight = 0, nMaxWeight = 0, nWeight = 0;
pwalletMain->GetStakeWeight(*pwalletMain, nMinWeight, nMaxWeight, nWeight);
@@ -87,6 +88,26 @@ Value getstakinginfo(const Array& params, bool fHelp)
bool staking = nLastCoinStakeSearchInterval && nWeight;
int nExpectedTime = staking ? (nTargetSpacing * nNetworkWeight / nWeight) : -1;
// Diagnostic: determine why staking might be disabled
Array stakingDisabledReasons;
if (!GetBoolArg("-staking", true))
stakingDisabledReasons.push_back("staking disabled via -staking=0 flag");
if (pwalletMain->IsLocked())
stakingDisabledReasons.push_back("wallet is locked (use walletpassphrase <pw> <timeout> true)");
if (vNodes.empty())
stakingDisabledReasons.push_back("no network connections (need at least 1 peer)");
if (IsInitialBlockDownload())
stakingDisabledReasons.push_back("initial block download in progress");
if (nWeight == 0)
stakingDisabledReasons.push_back("no mature coins available (coins need 520 confirmations)");
if (!staking && nLastCoinStakeSearchInterval == 0)
stakingDisabledReasons.push_back("stake miner thread not running");
Object obj;
obj.push_back(Pair("enabled", GetBoolArg("-staking", true)));
@@ -105,6 +126,16 @@ Value getstakinginfo(const Array& params, bool fHelp)
obj.push_back(Pair("expectedtime", nExpectedTime));
// Add detailed diagnostics
obj.push_back(Pair("walletlocked", pwalletMain->IsLocked()));
obj.push_back(Pair("walletunlockedforstakingonly", fWalletUnlockStakingOnly));
obj.push_back(Pair("connections", (int)vNodes.size()));
obj.push_back(Pair("initialblockdownload", IsInitialBlockDownload()));
obj.push_back(Pair("maturecoins", nWeight > 0));
if (!stakingDisabledReasons.empty())
obj.push_back(Pair("staking_disabled_reasons", stakingDisabledReasons));
return obj;
}
+1
View File
@@ -317,6 +317,7 @@ static const CRPCCommand vRPCCommands[] =
{ "getchaintips", &getchaintips, true, false },
{ "invalidateblock", &invalidateblock, false, false },
{ "reconsiderblock", &reconsiderblock, false, false },
{ "recalculatesupply", &recalculatesupply, false, false },
{ "reservebalance", &reservebalance, false, true},
{ "checkwallet", &checkwallet, false, true},
{ "repairwallet", &repairwallet, false, true},
+1
View File
@@ -225,6 +225,7 @@ extern json_spirit::Value getcheckpoint(const json_spirit::Array& params, bool f
extern json_spirit::Value getchaintips(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value invalidateblock(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value reconsiderblock(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value recalculatesupply(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value getaddressbalance(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value getaddressutxos(const json_spirit::Array& params, bool fHelp);
+35 -1
View File
@@ -162,7 +162,9 @@ bool CTxDB::TxnCommit()
delete activeBatch;
activeBatch = NULL;
if (!status.ok()) {
printf("LevelDB batch commit failure: %s\n", status.ToString().c_str());
printf("ERROR: LevelDB batch commit failure: %s\n", status.ToString().c_str());
printf("ERROR: This may indicate disk full, corruption, or permissions issue.\n");
printf("ERROR: Chain state may be inconsistent - immediate investigation required!\n");
return false;
}
return true;
@@ -904,3 +906,35 @@ bool CTxDB::HaveUtxo(const uint256& hash, unsigned int n)
return false;
}
int64_t CTxDB::SumUtxoValues(int& nCount)
{
nCount = 0;
int64_t nTotal = 0;
// Seek to the start of UTXO entries (key prefix "u")
CDataStream ssKeyPrefix(SER_DISK, CLIENT_VERSION);
ssKeyPrefix << make_pair(string("u"), make_pair(uint256(0), (unsigned int)0));
std::string strPrefixBegin = ssKeyPrefix.str();
leveldb::Iterator* it = pdb->NewIterator(leveldb::ReadOptions());
for (it->Seek(strPrefixBegin); it->Valid(); it->Next())
{
// Check key prefix is still "u"
CDataStream ssKey(it->key().data(), it->key().data() + it->key().size(), SER_DISK, CLIENT_VERSION);
std::string strKeyType;
ssKey >> strKeyType;
if (strKeyType != "u")
break;
// Deserialize the UTXO entry and sum the value
CDataStream ssValue(it->value().data(), it->value().data() + it->value().size(), SER_DISK, CLIENT_VERSION);
CUtxoEntry entry;
ssValue >> entry;
nTotal += entry.nValue;
nCount++;
}
delete it;
return nTotal;
}
+1
View File
@@ -236,6 +236,7 @@ public:
bool WriteUtxo(const uint256& hash, unsigned int n, const CUtxoEntry& entry);
bool EraseUtxo(const uint256& hash, unsigned int n);
bool HaveUtxo(const uint256& hash, unsigned int n);
int64_t SumUtxoValues(int& nCount);
private:
bool LoadBlockIndexGuts();
+2 -2
View File
@@ -41,10 +41,10 @@ const std::string CLIENT_NAME("Cryptographic Triangles");
#endif
#define BUILD_DESC_FROM_COMMIT(maj,min,rev,build,commit) \
"v" DO_STRINGIZE(maj) "." DO_STRINGIZE(min) "." DO_STRINGIZE(rev) "." DO_STRINGIZE(build) "" commit
"v" DO_STRINGIZE(maj) "." DO_STRINGIZE(min) "." DO_STRINGIZE(rev) "" commit
#define BUILD_DESC_FROM_UNKNOWN(maj,min,rev,build) \
"v" DO_STRINGIZE(maj) "." DO_STRINGIZE(min) "." DO_STRINGIZE(rev) "." DO_STRINGIZE(build) ""
"v" DO_STRINGIZE(maj) "." DO_STRINGIZE(min) "." DO_STRINGIZE(rev) ""
#ifndef BUILD_DESC
# ifdef GIT_COMMIT_ID
+38
View File
@@ -16,6 +16,40 @@ namespace fs = boost::filesystem;
static uint64_t nAccountingEntryNumber = 0;
extern bool fWalletUnlockStakingOnly;
//
// Auto-backup wallet before flush/rewrite operations.
// Copies wallet.dat to wallet.dat.auto.bak if the backup is older than the wallet.
// Returns true if backup was created or already up to date.
//
bool AutoBackupWallet(const fs::path& walletPath)
{
fs::path backupPath = walletPath.string() + ".auto.bak";
try {
// Only back up if wallet exists and is non-trivial (>1KB)
if (!fs::exists(walletPath))
return true;
uintmax_t walletSize = fs::file_size(walletPath);
if (walletSize < 1024) {
printf("AutoBackupWallet: wallet.dat is only %llu bytes (possibly corrupt), skipping auto-backup\n",
(unsigned long long)walletSize);
return false;
}
// Skip if backup exists and is same size (already backed up this version)
if (fs::exists(backupPath)) {
uintmax_t backupSize = fs::file_size(backupPath);
if (backupSize == walletSize)
return true;
}
fs::copy_file(walletPath, backupPath, fs::copy_options::overwrite_existing);
printf("AutoBackupWallet: backed up wallet.dat (%llu bytes) to wallet.dat.auto.bak\n",
(unsigned long long)walletSize);
return true;
} catch (const fs::filesystem_error& e) {
printf("AutoBackupWallet: failed - %s\n", e.what());
return false;
}
}
//
// CWalletDB
//
@@ -580,6 +614,10 @@ void ThreadFlushWalletDB(void* parg)
nLastFlushed = nWalletDBUpdated;
int64_t nStart = GetTimeMillis();
// Auto-backup before flush (protects against corruption)
fs::path walletPath = GetDataDir() / strFile;
AutoBackupWallet(walletPath);
// Flush wallet.dat so it's self contained
bitdb.CloseDb(strFile);
bitdb.CheckpointLSN(strFile);