v5.3.7: Fix sync stall, transaction display, balance updates, and bootstrap snapshots

Sync fixes:
- Extend stall detection beyond IBD to catch post-IBD sync gaps
- Walk-forward inv continuation to avoid CBlockLocator exponential gap loop
- Track walk-forward progress for stall recovery without restarting from scratch

GUI fixes:
- Load transactions synchronously in constructor (deferred QTimer never fired)
- Use beginResetModel/endResetModel instead of deprecated reset()
- Schedule full refresh on TRY_LOCK failure to avoid dropped CT_NEW notifications
- Only update cachedNumBlocks after successful balance check (prevents permanent loss)
- Add GetAllBalances() single-pass balance retrieval with TRY_LOCK

Bootstrap:
- Add trusted snapshot manifest verification for bootstrap archives
- Add IsKnownCheckpoint() to validate manifest against compiled-in checkpoints
- Skip txleveldb rebuild when verified manifest is present

Bump version to 5.3.7 across all packaging manifests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-24 23:36:43 -07:00
parent 3be4d18b81
commit 9f69ead715
32 changed files with 360 additions and 73 deletions
+1 -1
View File
@@ -9,7 +9,7 @@ on:
workflow_dispatch:
env:
VERSION: "5.3.6"
VERSION: "5.3.7"
jobs:
test-linux-unit:
+1 -1
View File
@@ -37,7 +37,7 @@ No building required. Download the pre-built Linux binary from GitHub:
```bash
cd /tmp
curl -L -o trianglesd https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.3.6/Cryptographic-Triangles-v5.3.6-linux-x64-daemon
curl -L -o trianglesd https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.3.7/Cryptographic-Triangles-v5.3.7-linux-x64-daemon
chmod +x trianglesd
sudo mv trianglesd /usr/local/bin/
```
+3 -3
View File
@@ -3,7 +3,7 @@
# Generated by qmake (3.1) (Qt 5.15.18)
# Project: triangles-qt.pro
# Template: app
# Command: C:/msys64/mingw64/bin/qmake-qt5.exe -o Makefile triangles-qt.pro -spec win32-g++ CONFIG+=release
# Command: C:/msys64/mingw64/bin/qmake-qt5.exe -o Makefile triangles-qt.pro
#############################################################################
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 -spec win32-g++ CONFIG+=release
$(QMAKE) -o Makefile triangles-qt.pro
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 -spec win32-g++ CONFIG+=release
@$(QMAKE) -o Makefile triangles-qt.pro
qmake_all: FORCE
+1 -1
View File
@@ -3,7 +3,7 @@
# Run on a Linux x64 system with appimagetool installed
set -e
VERSION="5.3.6"
VERSION="5.3.7"
APPDIR="Triangles-x86_64.AppDir"
RELEASE_URL="https://github.com/SamiAhmed7777/triangles_v5/releases/download/v${VERSION}"
@@ -41,6 +41,11 @@
</provides>
<releases>
<release version="5.3.7" date="2026-03-24">
<description>
<p>Version 5.3.7 release.</p>
</description>
</release>
<release version="5.3.6" date="2026-03-23">
<description>
<p>IBD sync optimizations, Linux build fixes, and modern compiler support.</p>
+1 -1
View File
@@ -1,6 +1,6 @@
# Maintainer: Cryptographic Triangles Team
pkgname=triangles-qt-bin
pkgver=5.3.6
pkgver=5.3.7
pkgrel=1
pkgdesc="Cryptographic Triangles (TRI) cryptocurrency wallet - Qt GUI"
arch=('x86_64')
@@ -3,7 +3,7 @@ $ErrorActionPreference = 'Stop'
$packageArgs = @{
packageName = 'triangles'
unzipLocation = "$(Split-Path -Parent $MyInvocation.MyCommand.Definition)"
url64bit = 'https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.3.6/Cryptographic-Triangles-5.3.6-win-x64.zip'
url64bit = 'https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.3.7/Cryptographic-Triangles-5.3.7-win-x64.zip'
checksum64 = '6f002a669a7e92aaf3d8dd7b1ae80f06a086c99a15ca05cf107665009ffc06b7'
checksumType64 = 'sha256'
}
+2 -2
View File
@@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd">
<metadata>
<id>triangles</id>
<version>5.3.6</version>
<version>5.3.7</version>
<title>Cryptographic Triangles</title>
<authors>Cryptographic Triangles Team</authors>
<owners>SamiAhmed7777</owners>
@@ -25,6 +25,6 @@ featuring the unique Hash9 algorithm (13-step hash cascade).
- Encrypted peer-to-peer messaging
- Tor v3 integration for anonymous transactions
</description>
<releaseNotes>https://github.com/SamiAhmed7777/triangles_v5/releases/tag/v5.3.6</releaseNotes>
<releaseNotes>https://github.com/SamiAhmed7777/triangles_v5/releases/tag/v5.3.7</releaseNotes>
</metadata>
</package>
+1 -1
View File
@@ -1,5 +1,5 @@
Package: triangles
Version: 5.3.6-1
Version: 5.3.7-1
Section: net
Priority: optional
Architecture: amd64
+1 -1
View File
@@ -3,7 +3,7 @@
# Run from the packaging/debian directory
set -e
VERSION="5.3.6"
VERSION="5.3.7"
PKGDIR="triangles_${VERSION}-1_amd64"
RELEASE_URL="https://github.com/SamiAhmed7777/triangles_v5/releases/download/v${VERSION}"
+2 -2
View File
@@ -2,9 +2,9 @@ FROM ubuntu:22.04
LABEL maintainer="Cryptographic Triangles Team"
LABEL description="Cryptographic Triangles (TRI) headless daemon"
LABEL version="5.3.6"
LABEL version="5.3.7"
ARG VERSION=5.3.6
ARG VERSION=5.3.7
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
+1 -1
View File
@@ -3,7 +3,7 @@ version: "3.8"
services:
trianglesd:
build: .
image: cryptographic-triangles/trianglesd:5.3.6
image: cryptographic-triangles/trianglesd:5.3.7
container_name: trianglesd
restart: unless-stopped
ports:
@@ -25,7 +25,7 @@ modules:
- install -Dm644 org.cryptographic_triangles.TrianglesQt.metainfo.xml /app/share/metainfo/org.cryptographic_triangles.TrianglesQt.metainfo.xml
sources:
- type: file
url: https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.3.6/Cryptographic-Triangles-v5.3.6-linux-x64-qt
url: https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.3.7/Cryptographic-Triangles-v5.3.7-linux-x64-qt
sha256: ed220eb8d0b403f62cdac28988541fd1a27864491e233216f9c00a4c2537b4a3
dest-filename: triangles-qt-linux
- type: file
@@ -55,6 +55,6 @@ modules:
- install -Dm755 trianglesd-linux /app/bin/trianglesd
sources:
- type: file
url: https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.3.6/Cryptographic-Triangles-v5.3.6-linux-x64-daemon
url: https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.3.7/Cryptographic-Triangles-v5.3.7-linux-x64-daemon
sha256: 4d2ab25d61127d6aff3e6f3069556d04f4b823f8849e97629c12871ad4779517
dest-filename: trianglesd-linux
+4 -4
View File
@@ -2,15 +2,15 @@ class Triangles < Formula
desc "Cryptographic Triangles (TRI) cryptocurrency wallet and daemon"
homepage "https://cryptographic-triangles.org"
license "MIT"
version "5.3.6"
version "5.3.7"
on_macos do
url "https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.3.6/Cryptographic-Triangles-v5.3.6-macos-arm64.dmg"
url "https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.3.7/Cryptographic-Triangles-v5.3.7-macos-arm64.dmg"
sha256 "3a58e795d898656b455fd639c0ea826a4457d390a64d00ace9a1257598d053be"
end
on_linux do
url "https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.3.6/Cryptographic-Triangles-v5.3.6-linux-x64-daemon"
url "https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.3.7/Cryptographic-Triangles-v5.3.7-linux-x64-daemon"
sha256 "4d2ab25d61127d6aff3e6f3069556d04f4b823f8849e97629c12871ad4779517"
end
@@ -21,7 +21,7 @@ class Triangles < Formula
prefix.install "Triangles-Qt.app"
bin.write_exec_script prefix/"Triangles-Qt.app/Contents/MacOS/Triangles-Qt"
else
bin.install "Cryptographic-Triangles-v5.3.6-linux-x64-daemon" => "trianglesd"
bin.install "Cryptographic-Triangles-v5.3.7-linux-x64-daemon" => "trianglesd"
end
end
+1 -1
View File
@@ -14,7 +14,7 @@
}:
let
version = "5.3.6";
version = "5.3.7";
desktopItem = makeDesktopItem {
name = "triangles-qt";
+1 -1
View File
@@ -4,7 +4,7 @@
# Install build tools: sudo dnf install rpm-build rpmdevtools
set -e
VERSION="5.3.6"
VERSION="5.3.7"
RELEASE_URL="https://github.com/SamiAhmed7777/triangles_v5/releases/download/v${VERSION}"
echo "Building RPM for Triangles v${VERSION}..."
+1 -1
View File
@@ -1,5 +1,5 @@
Name: triangles
Version: 5.3.6
Version: 5.3.7
Release: 1%{?dist}
Summary: Cryptographic Triangles (TRI) cryptocurrency wallet
License: MIT
@@ -1,5 +1,5 @@
PackageIdentifier: CryptographicTriangles.TrianglesQt
PackageVersion: 5.3.6
PackageVersion: 5.3.7
PackageLocale: en-US
Publisher: Cryptographic Triangles
PublisherUrl: https://cryptographic-triangles.org
@@ -27,7 +27,7 @@ Installers:
- RelativeFilePath: triangles-qt.exe
PortableCommandAlias: triangles-qt
ArchiveBinariesDependOnPath: true
InstallerUrl: https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.3.6/Cryptographic-Triangles-5.3.6-win-x64.zip
InstallerUrl: https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.3.7/Cryptographic-Triangles-5.3.7-win-x64.zip
InstallerSha256: 6F002A669A7E92AAF3D8DD7B1AE80F06A086C99A15CA05CF107665009FFC06B7
ManifestType: singleton
ManifestVersion: 1.6.0
+5 -5
View File
@@ -1,6 +1,6 @@
name: triangles
base: core22
version: '5.3.6'
version: '5.3.7'
summary: Cryptographic Triangles (TRI) cryptocurrency wallet
description: |
Privacy-focused cryptocurrency featuring Proof-of-Stake consensus,
@@ -51,10 +51,10 @@ apps:
parts:
triangles:
plugin: dump
source: https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.3.6/Cryptographic-Triangles-v5.3.6-linux-x64-qt
source: https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.3.7/Cryptographic-Triangles-v5.3.7-linux-x64-qt
source-type: file
organize:
Cryptographic-Triangles-v5.3.6-linux-x64-qt: bin/triangles-qt
Cryptographic-Triangles-v5.3.7-linux-x64-qt: bin/triangles-qt
stage-packages:
- libqt5widgets5
- libqt5gui5
@@ -73,10 +73,10 @@ parts:
trianglesd:
plugin: dump
source: https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.3.6/Cryptographic-Triangles-v5.3.6-linux-x64-daemon
source: https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.3.7/Cryptographic-Triangles-v5.3.7-linux-x64-daemon
source-type: file
organize:
Cryptographic-Triangles-v5.3.6-linux-x64-daemon: bin/trianglesd
Cryptographic-Triangles-v5.3.7-linux-x64-daemon: bin/trianglesd
desktop-entry:
plugin: dump
+156 -3
View File
@@ -10,10 +10,18 @@
#include <zlib.h>
#include "version.h"
#include "uint256.h"
#include <fstream>
#include <sstream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
// Forward declarations to avoid pulling in heavy consensus headers
extern bool fTestNet;
namespace Checkpoints { bool IsKnownCheckpoint(int nHeight, const uint256& hash); }
namespace fs = boost::filesystem;
using boost::asio::ip::tcp;
@@ -311,6 +319,112 @@ static bool ExtractTarGz(const fs::path& tarGzPath,
} // anonymous namespace
bool ParseManifest(const fs::path& manifestPath,
SnapshotManifest& manifest,
std::string& strError)
{
std::ifstream in(manifestPath.string().c_str());
if (!in.is_open()) {
strError = "Cannot open " + manifestPath.string();
return false;
}
manifest.format = 0;
manifest.network.clear();
manifest.height = -1;
manifest.hash.clear();
manifest.dbversion = 0;
std::string line;
while (std::getline(in, line)) {
boost::trim(line);
if (line.empty() || line[0] == '#')
continue;
size_t eq = line.find('=');
if (eq == std::string::npos)
continue;
std::string key = line.substr(0, eq);
std::string val = line.substr(eq + 1);
boost::trim(key);
boost::trim(val);
if (key == "format")
manifest.format = std::atoi(val.c_str());
else if (key == "network")
manifest.network = val;
else if (key == "height")
manifest.height = std::atoi(val.c_str());
else if (key == "hash")
manifest.hash = val;
else if (key == "dbversion")
manifest.dbversion = std::atoi(val.c_str());
}
in.close();
if (manifest.format == 0) {
strError = "Manifest missing 'format' field";
return false;
}
if (manifest.network.empty()) {
strError = "Manifest missing 'network' field";
return false;
}
if (manifest.height < 0) {
strError = "Manifest missing or invalid 'height' field";
return false;
}
if (manifest.hash.empty()) {
strError = "Manifest missing 'hash' field";
return false;
}
if (manifest.dbversion == 0) {
strError = "Manifest missing 'dbversion' field";
return false;
}
return true;
}
bool VerifyManifest(const SnapshotManifest& manifest,
std::string& strError)
{
if (manifest.format != 1) {
strError = "Unsupported manifest format: " + std::to_string(manifest.format);
return false;
}
std::string expectedNetwork = fTestNet ? "test" : "main";
if (manifest.network != expectedNetwork) {
strError = "Network mismatch: manifest says '" + manifest.network
+ "', expected '" + expectedNetwork + "'";
return false;
}
if (manifest.dbversion != DATABASE_VERSION) {
strError = "DB version mismatch: manifest says "
+ std::to_string(manifest.dbversion)
+ ", binary expects " + std::to_string(DATABASE_VERSION);
return false;
}
uint256 manifestHash(manifest.hash);
if (manifestHash == 0) {
strError = "Invalid hash in manifest: " + manifest.hash;
return false;
}
if (!Checkpoints::IsKnownCheckpoint(manifest.height, manifestHash)) {
strError = "Height " + std::to_string(manifest.height)
+ " / hash " + manifest.hash
+ " is not a known checkpoint";
return false;
}
return true;
}
bool DownloadBootstrap(const std::string& host,
const fs::path& dataDir,
ProgressCallback progressFn,
@@ -362,16 +476,55 @@ bool DownloadBootstrap(const std::string& host,
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.
// Check if the archive included a trusted pre-built index (txleveldb/)
// with a valid snapshot.manifest. If verified, keep it to skip the
// multi-hour FastImportBlockFile() rebuild.
fs::path txleveldb = dataDir / "txleveldb";
fs::path database = dataDir / "database";
fs::path manifestPath = dataDir / "snapshot.manifest";
bool keepIndex = false;
if (fs::exists(manifestPath) && fs::exists(txleveldb)) {
SnapshotManifest manifest;
std::string manifestError;
if (ParseManifest(manifestPath, manifest, manifestError)) {
printf("Bootstrap: snapshot.manifest found (format=%d, network=%s, "
"height=%d, dbversion=%d)\n",
manifest.format, manifest.network.c_str(),
manifest.height, manifest.dbversion);
if (VerifyManifest(manifest, manifestError)) {
printf("Bootstrap: manifest verified - keeping pre-built index "
"(height %d, checkpoint match)\n", manifest.height);
keepIndex = true;
} else {
printf("Bootstrap: manifest verification failed: %s\n",
manifestError.c_str());
}
} else {
printf("Bootstrap: cannot parse snapshot.manifest: %s\n",
manifestError.c_str());
}
}
if (!keepIndex) {
// No valid manifest or verification failed - delete the index.
// FastImportBlockFile() will rebuild from blk0001.dat on next startup.
printf("Bootstrap: removing extracted txleveldb/ (will rebuild index from blk0001.dat)\n");
if (fs::exists(txleveldb))
fs::remove_all(txleveldb);
}
// Always remove BDB database/ dir (wallet environment from another machine)
if (fs::exists(database))
fs::remove_all(database);
// Clean up manifest file (not needed after verification)
if (fs::exists(manifestPath))
fs::remove(manifestPath);
return true;
}
+18
View File
@@ -41,6 +41,24 @@ namespace Bootstrap {
ProgressCallback progressFn,
std::string& strError);
// Snapshot manifest (parsed from snapshot.manifest in bootstrap archive)
struct SnapshotManifest {
int format; // format version, must be 1
std::string network; // "main" or "test"
int height; // block height of the snapshot tip
std::string hash; // block hash at that height (hex, no 0x prefix)
int dbversion; // DATABASE_VERSION the txleveldb was built with
};
// Parse a snapshot.manifest file into a SnapshotManifest struct.
bool ParseManifest(const boost::filesystem::path& manifestPath,
SnapshotManifest& manifest,
std::string& strError);
// Verify a parsed manifest against compiled-in checkpoints and config.
bool VerifyManifest(const SnapshotManifest& manifest,
std::string& strError);
} // namespace Bootstrap
#endif // TRIANGLES_BOOTSTRAP_H
+8
View File
@@ -60,6 +60,14 @@ namespace Checkpoints
return hash == i->second;
}
bool IsKnownCheckpoint(int nHeight, const uint256& hash)
{
MapCheckpoints& checkpoints = (fTestNet ? mapCheckpointsTestnet : mapCheckpoints);
MapCheckpoints::const_iterator i = checkpoints.find(nHeight);
if (i == checkpoints.end()) return false;
return hash == i->second;
}
int GetTotalBlocksEstimate()
{
MapCheckpoints& checkpoints = (fTestNet ? mapCheckpointsTestnet : mapCheckpoints);
+3
View File
@@ -39,6 +39,9 @@ namespace Checkpoints
// Returns true if block passes checkpoint checks
bool CheckHardened(int nHeight, const uint256& hash);
// Returns true only if (nHeight, hash) is an exact entry in mapCheckpoints
bool IsKnownCheckpoint(int nHeight, const uint256& hash);
// Return conservative estimate of total number of blocks, 0 if unknown
int GetTotalBlocksEstimate();
+1 -1
View File
@@ -8,7 +8,7 @@
// These need to be macros, as version.cpp's and triangles-qt.rc's voodoo requires it
#define CLIENT_VERSION_MAJOR 5
#define CLIENT_VERSION_MINOR 3
#define CLIENT_VERSION_REVISION 6
#define CLIENT_VERSION_REVISION 7
#define CLIENT_VERSION_BUILD 0
// Converts the parameter X to a string after macro replacement on X has been performed.
+16
View File
@@ -880,6 +880,22 @@ bool AppInit2()
}
printf(" block index %15" PRId64 "ms\n", GetTimeMillis() - nStart);
// Diagnostic: check for blocks in mapBlockIndex above pindexBest
{
int nMaxIndexHeight = 0;
int nAboveBest = 0;
for (std::map<uint256, CBlockIndex*>::iterator it = mapBlockIndex.begin();
it != mapBlockIndex.end(); ++it)
{
if (it->second->nHeight > nMaxIndexHeight)
nMaxIndexHeight = it->second->nHeight;
if (it->second->nHeight > nBestHeight)
nAboveBest++;
}
printf("SYNC-DIAG: mapBlockIndex=%d entries, maxHeight=%d, bestHeight=%d, aboveBest=%d\n",
(int)mapBlockIndex.size(), nMaxIndexHeight, nBestHeight, nAboveBest);
}
if (GetBoolArg("-printblockindex") || GetBoolArg("-printblocktree"))
{
PrintBlockTree();
+59 -14
View File
@@ -60,6 +60,8 @@ int nCoinbaseMaturity = 7; //overall maturity: currently 7 blocks, maybe subject
CBlockIndex* pindexGenesisBlock = NULL;
int nBestHeight = -1;
int nHighestInvWalk = 0; // height of walk-forward progress through already-have inv
uint256 hashHighestInvWalk = 0; // hash of that block
uint256 nBestChainTrust = 0;
uint256 nBestInvalidTrust = 0;
@@ -3941,7 +3943,8 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
nBlockInv, nTxInv, pfrom->addr.ToString().c_str(), nBestHeight);
CTxDB txdb("r");
int nNew = 0, nAlready = 0;
int nNew = 0, nAlready = 0, nAboveBest = 0;
int nFirstInvHeight = -1, nLastInvHeight = -1;
for (unsigned int nInv = 0; nInv < vInv.size(); nInv++)
{
const CInv &inv = vInv[nInv];
@@ -3952,7 +3955,18 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
bool fAlreadyHave = AlreadyHave(txdb, inv);
if (inv.type == MSG_BLOCK) {
if (fAlreadyHave) nAlready++; else nNew++;
if (fAlreadyHave) {
nAlready++;
std::map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(inv.hash);
if (mi != mapBlockIndex.end()) {
int h = mi->second->nHeight;
if (nFirstInvHeight == -1) nFirstInvHeight = h;
nLastInvHeight = h;
if (h > nBestHeight) nAboveBest++;
}
} else {
nNew++;
}
}
if (!fAlreadyHave)
@@ -3960,15 +3974,30 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
else if (inv.type == MSG_BLOCK && mapOrphanBlocks.count(inv.hash)) {
pfrom->PushGetBlocks(pindexBest, GetOrphanRoot(mapOrphanBlocks[inv.hash]));
} else if (nInv == nLastBlock) {
// Continuation: walk forward from the last inv block.
// Don't jump to pindexBest — its CBlockLocator exponential
// spacing can map back to the same old match point, looping.
// Walking from the last inv block progresses linearly through
// the "already have" zone until we reach new blocks.
int nInvH = mapBlockIndex[inv.hash]->nHeight;
if (nInvH > nHighestInvWalk) {
nHighestInvWalk = nInvH;
hashHighestInvWalk = inv.hash;
}
pfrom->pindexLastGetBlocksBegin = NULL; // reset dedup
pfrom->PushGetBlocks(mapBlockIndex[inv.hash], uint256(0));
printf("IBD-DIAG: inv last block already known, pushing getblocks from %d\n",
mapBlockIndex[inv.hash]->nHeight);
printf("SYNC-DIAG: inv walk-forward from %d (best=%d, walk=%d)\n",
nInvH, nBestHeight, nHighestInvWalk);
}
Inventory(inv.hash);
}
if (nBlockInv > 0)
printf("IBD-DIAG: inv result: %d new blocks requested, %d already have\n", nNew, nAlready);
if (nBlockInv > 0) {
printf("SYNC-DIAG: inv result: %d new, %d already have (%d above best=%d), range=%d..%d\n",
nNew, nAlready, nAboveBest, nBestHeight, nFirstInvHeight, nLastInvHeight);
if (nNew > 0 && nAlready > 0)
printf("SYNC-DIAG: *** FORK POINT CROSSED *** - downloading %d new blocks from canonical chain\n", nNew);
}
}
@@ -4770,27 +4799,43 @@ bool SendMessages(CNode* pto, bool fSendTrickle)
//
// Stall detection: if IBD and no new blocks for 5 seconds, re-request.
// Tighter than the old 10s to rotate away from slow peers faster.
// Stall detection: if we're still catching up and no new blocks for
// a while, re-request. Active during IBD (5s timeout) and also
// post-IBD when we're behind peers (30s timeout) to handle the case
// where IBD flips to false during a transient download gap.
//
if (IsInitialBlockDownload() && !pto->fClient)
if (!pto->fClient && nBestHeight < GetNumBlocksOfPeers())
{
static int64_t nLastBlockReceived = 0;
static int nLastHeight = 0;
static int64_t nLastStallLog = 0;
int nStallTimeout = IsInitialBlockDownload() ? 5 : 15;
if (nBestHeight > nLastHeight) {
nLastHeight = nBestHeight;
nLastBlockReceived = GetTime();
} else if (nLastBlockReceived > 0 && GetTime() - nLastBlockReceived > 5) {
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),
} else if (nLastBlockReceived > 0 && GetTime() - nLastBlockReceived > nStallTimeout) {
if (GetTime() - nLastStallLog >= 15) { // log every 15s max
printf("SYNC-DIAG: STALL at height %d/%d for %ds (IBD=%d walk=%d), peer=%s askfor_queue=%d\n",
nBestHeight, GetNumBlocksOfPeers(),
(int)(GetTime() - nLastBlockReceived),
IsInitialBlockDownload(), nHighestInvWalk,
pto->addr.ToString().c_str(),
(int)pto->mapAskFor.size(), (int)pto->nSendSize);
(int)pto->mapAskFor.size());
nLastStallLog = GetTime();
}
// Use the walk-forward progress point if available, to avoid
// restarting from pindexBest (which hits the CBlockLocator
// exponential gap and starts the walk-forward from scratch).
pto->pindexLastGetBlocksBegin = NULL;
if (nHighestInvWalk > nBestHeight && hashHighestInvWalk != 0 &&
mapBlockIndex.count(hashHighestInvWalk))
{
pto->PushGetBlocks(mapBlockIndex[hashHighestInvWalk], uint256(0));
printf("SYNC-DIAG: stall re-request from walk=%d (not best=%d)\n",
nHighestInvWalk, nBestHeight);
} else {
pto->PushGetBlocks(pindexBest, uint256(0));
}
nLastBlockReceived = GetTime();
}
}
+20 -1
View File
@@ -19,6 +19,7 @@
#include <QMenu>
#include <QFont>
#include <QColor>
#include <QTextDocument>
Q_DECLARE_METATYPE(std::vector<unsigned char>);
@@ -27,6 +28,24 @@ QList<QString> ambiguous; /**< Specifies Ambiguous addresses */
const QString MessageModel::Sent = "Sent";
const QString MessageModel::Received = "Received";
namespace {
static QString FormatShortMessage(const QString& message)
{
static const int kMaxPreviewChars = 80;
QTextDocument doc;
doc.setHtml(message);
QString preview = doc.toPlainText().simplified();
if (preview.length() <= kMaxPreviewChars)
return preview;
return preview.left(kMaxPreviewChars - 3) + "...";
}
} // namespace
struct MessageTableEntryLessThan
{
bool operator()(const MessageTableEntry &a, const MessageTableEntry &b) const {return a.received_datetime < b.received_datetime;};
@@ -485,7 +504,7 @@ QVariant MessageModel::data(const QModelIndex &index, int role) const
case FilterAddressRole: return (rec->type == MessageTableEntry::Sent ? rec->to_address + rec->from_address : rec->from_address + rec->to_address);
case LabelRole: return rec->label;
case MessageRole: return rec->message;
case ShortMessageRole: return rec->message; // TODO: Short message
case ShortMessageRole: return FormatShortMessage(rec->message);
case HTMLRole: return rec->received_datetime.toString() + "<br>" + (rec->label.isEmpty() ? rec->from_address : rec->label) + "<br>" + rec->message;
case Ambiguous:
int it;
+22 -11
View File
@@ -52,11 +52,13 @@ class TransactionTablePriv
public:
TransactionTablePriv(CWallet *wallet, TransactionTableModel *parent):
wallet(wallet),
parent(parent)
parent(parent),
fInitialLoadDone(false)
{
}
CWallet *wallet;
TransactionTableModel *parent;
bool fInitialLoadDone;
/* Local cache of wallet.
* As it is in the same order as the CWallet, by definition
@@ -68,22 +70,19 @@ public:
*/
void refreshWallet()
{
OutputDebugStringF("refreshWallet\n");
OutputDebugStringF("refreshWallet: fInitialLoadDone=%d mapWallet.size=%u\n",
(int)fInitialLoadDone, (unsigned)wallet->mapWallet.size());
cachedWallet.clear();
{
TRY_LOCK(wallet->cs_wallet, lockWallet);
if(!lockWallet)
{
// Lock busy (block processing), retry in 500ms
QTimer::singleShot(500, parent, SLOT(refreshWallet()));
return;
}
LOCK(wallet->cs_wallet);
for(std::map<uint256, CWalletTx>::iterator it = wallet->mapWallet.begin(); it != wallet->mapWallet.end(); ++it)
{
if(TransactionRecord::showTransaction(it->second))
cachedWallet.append(TransactionRecord::decomposeTransaction(wallet, it->second));
}
fInitialLoadDone = true;
}
OutputDebugStringF("refreshWallet: loaded %d transaction records\n", cachedWallet.size());
}
/* Update our model of the wallet incrementally, to synchronize our model of the wallet
@@ -97,7 +96,12 @@ public:
{
TRY_LOCK(wallet->cs_wallet, lockWallet);
if (!lockWallet)
{
// Lock busy - schedule a full refresh to pick up missed updates.
// This avoids silently dropping CT_NEW notifications.
QTimer::singleShot(500, parent, SLOT(refreshWallet()));
return;
}
// Find transaction in wallet
std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(hash);
@@ -243,7 +247,12 @@ TransactionTableModel::TransactionTableModel(CWallet* wallet, WalletModel *paren
cachedNumBlocks(0)
{
columns << QString() << tr("Date") << tr("Type") << tr("Address") << tr("Amount");
QTimer::singleShot(0, this, SLOT(refreshWallet()));
// Load transactions synchronously in the constructor so they're
// available before the event loop starts. The deferred QTimer approach
// was never firing because queued updateTransaction events from sync
// would flood the event queue first.
priv->refreshWallet();
QTimer *timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(updateConfirmations()));
@@ -267,8 +276,10 @@ void TransactionTableModel::updateTransaction(const QString &hash, int status)
void TransactionTableModel::refreshWallet()
{
beginResetModel();
priv->refreshWallet();
reset();
endResetModel();
OutputDebugStringF("TransactionTableModel::refreshWallet: rowCount=%d\n", priv->size());
}
void TransactionTableModel::updateConfirmations()
+7 -4
View File
@@ -78,20 +78,22 @@ void WalletModel::pollBalanceChanged()
{
if(nBestHeight != cachedNumBlocks)
{
// Balance and number of transactions might have changed
// Balance and number of transactions might have changed.
// Only update cachedNumBlocks AFTER a successful balance check,
// otherwise a TRY_LOCK failure loses the update permanently.
if(checkBalanceChanged())
cachedNumBlocks = nBestHeight;
checkBalanceChanged();
}
}
void WalletModel::checkBalanceChanged()
bool WalletModel::checkBalanceChanged()
{
// 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;
return false;
if(cachedBalance != newBalance || cachedStake != newStake || cachedUnconfirmedBalance != newUnconfirmedBalance || cachedImmatureBalance != newImmatureBalance)
{
@@ -101,6 +103,7 @@ void WalletModel::checkBalanceChanged()
cachedImmatureBalance = newImmatureBalance;
emit balanceChanged(newBalance, newStake, newUnconfirmedBalance, newImmatureBalance);
}
return true;
}
void WalletModel::updateTransaction(const QString &hash, int status)
+1 -1
View File
@@ -152,7 +152,7 @@ private:
void subscribeToCoreSignals();
void unsubscribeFromCoreSignals();
void checkBalanceChanged();
bool checkBalanceChanged();
public slots:
+7 -1
View File
@@ -787,10 +787,16 @@ static void RPCAcceptHandler(boost::shared_ptr< basic_socket_acceptor<Protocol,
AcceptedConnectionImpl<ip::tcp>* tcp_conn = dynamic_cast< AcceptedConnectionImpl<ip::tcp>* >(conn);
// TODO: Actually handle errors
if (error)
{
if (error != asio::error::operation_aborted)
printf("RPC accept error from %s: %s (%d)\n",
tcp_conn ? tcp_conn->peer.address().to_string().c_str() : "unknown peer",
error.message().c_str(),
error.value());
delete conn;
vnThreadsRunning[THREAD_RPCLISTENER]--;
return;
}
// Restrict callers by IP. It is important to
+1 -1
View File
@@ -53,7 +53,7 @@ static const int MEMPOOL_GD_VERSION = 60002;
#define DISPLAY_VERSION_MAJOR 5
#define DISPLAY_VERSION_MINOR 3
#define DISPLAY_VERSION_REVISION 6
#define DISPLAY_VERSION_REVISION 7
#define DISPLAY_VERSION_BUILD 0
#endif