Compare commits

...

2 Commits

Author SHA1 Message Date
sami7777 96fb7d5040 Bump version to 5.3.4 - fix UI freezing during staking
Build All Platforms / build-windows-qt (push) Waiting to run
Build All Platforms / build-windows-daemon (push) Waiting to run
Build All Platforms / build-linux-qt (push) Waiting to run
Build All Platforms / build-linux-daemon (push) Waiting to run
Build All Platforms / build-macos (push) Waiting to run
Build All Platforms / release (push) Blocked by required conditions
Move GetStakeWeight() off the UI thread by caching in the staking
miner thread. Replace blocking LOCK(cs_vNodes) with TRY_LOCK in
clientmodel and staking icon updates. Fix out-of-sync label getting
stuck when disconnected. Add daemon bootstrap and faster IBD pipeline.

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

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-19 21:35:12 -07:00
21 changed files with 663 additions and 100 deletions
+1 -1
View File
@@ -9,7 +9,7 @@ on:
workflow_dispatch: workflow_dispatch:
env: env:
VERSION: "5.3.3" VERSION: "5.3.4"
jobs: jobs:
build-windows-qt: build-windows-qt:
+3 -3
View File
@@ -3,7 +3,7 @@
# Generated by qmake (3.1) (Qt 5.15.18) # Generated by qmake (3.1) (Qt 5.15.18)
# Project: triangles-qt.pro # Project: triangles-qt.pro
# Template: app # 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 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/lib/qtmain.prl \
C:/msys64/mingw64/share/qt5/mkspecs/features/build_pass.prf \ C:/msys64/mingw64/share/qt5/mkspecs/features/build_pass.prf \
src/qt/triangles.qrc 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/features/spec_pre.prf:
C:/msys64/mingw64/share/qt5/mkspecs/qdevice.pri: C:/msys64/mingw64/share/qt5/mkspecs/qdevice.pri:
C:/msys64/mingw64/share/qt5/mkspecs/features/device_config.prf: 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: C:/msys64/mingw64/share/qt5/mkspecs/features/build_pass.prf:
src/qt/triangles.qrc: src/qt/triangles.qrc:
qmake: FORCE 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 qmake_all: FORCE
+194
View File
@@ -8,9 +8,12 @@
#include <boost/filesystem/fstream.hpp> #include <boost/filesystem/fstream.hpp>
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#include <zlib.h>
#include <fstream> #include <fstream>
#include <sstream> #include <sstream>
#include <cstdio> #include <cstdio>
#include <cstring>
namespace fs = boost::filesystem; namespace fs = boost::filesystem;
using boost::asio::ip::tcp; using boost::asio::ip::tcp;
@@ -181,4 +184,195 @@ bool FetchFileList(const std::string& host,
return true; 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 } // namespace Bootstrap
+7
View File
@@ -34,6 +34,13 @@ namespace Bootstrap {
std::vector<std::string>& files, std::vector<std::string>& files,
std::string& strError); 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 } // namespace Bootstrap
#endif // TRIANGLES_BOOTSTRAP_H #endif // TRIANGLES_BOOTSTRAP_H
+1 -1
View File
@@ -8,7 +8,7 @@
// These need to be macros, as version.cpp's and triangles-qt.rc's voodoo requires it // 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_MAJOR 5
#define CLIENT_VERSION_MINOR 3 #define CLIENT_VERSION_MINOR 3
#define CLIENT_VERSION_REVISION 3 #define CLIENT_VERSION_REVISION 4
#define CLIENT_VERSION_BUILD 0 #define CLIENT_VERSION_BUILD 0
// Converts the parameter X to a string after macro replacement on X has been performed. // Converts the parameter X to a string after macro replacement on X has been performed.
+48
View File
@@ -12,6 +12,7 @@
#include "checkpoints.h" #include "checkpoints.h"
#include "smessage.h" #include "smessage.h"
#include "openssl_compat.h" #include "openssl_compat.h"
#include "bootstrap.h"
#include "tor/tor_embedded.h" #include "tor/tor_embedded.h"
#include "tor/onion_v3.h" #include "tor/onion_v3.h"
#include "tor/tor_process.h" #include "tor/tor_process.h"
@@ -797,6 +798,43 @@ bool AppInit2()
for (string strDest : mapMultiArgs["-seednode"]) for (string strDest : mapMultiArgs["-seednode"])
AddOneShot(strDest); 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 // ********************************************************* Step 7: load blockchain
if (!bitdb.Open(GetDataDir())) if (!bitdb.Open(GetDataDir()))
@@ -821,6 +859,16 @@ bool AppInit2()
if (!LoadBlockIndex()) if (!LoadBlockIndex())
return InitError(_("Error loading blkindex.dat")); 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 // as LoadBlockIndex can take several minutes, it's possible the user
// requested to kill triangles-qt during the last operation. If so, exit. // requested to kill triangles-qt during the last operation. If so, exit.
+239 -9
View File
@@ -3023,7 +3023,14 @@ bool LoadExternalBlockFile(FILE* fileIn)
{ {
int64_t nStart = GetTimeMillis(); 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; int nLoaded = 0;
int64_t nLastProgressReport = 0;
{ {
LOCK(cs_main); LOCK(cs_main);
try { try {
@@ -3074,6 +3081,20 @@ bool LoadExternalBlockFile(FILE* fileIn)
nLoaded++; nLoaded++;
nPos += 4 + nSize; 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) { catch (std::exception &e) {
@@ -3085,6 +3106,210 @@ bool LoadExternalBlockFile(FILE* fileIn)
return nLoaded > 0; 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 // CAlert
@@ -3497,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 // Trigger them to send a getblocks request for the next batch of inventory
if (inv.hash == pfrom->hashContinue) if (inv.hash == pfrom->hashContinue)
{ {
// triangles: send latest proof-of-work block to allow the // Send the best block hash to trigger the next getblocks.
// download node to accept as orphan (proof-of-stake // Original code sent the last PoW block, but since PoW ended
// block might be rejected by stake connection check) // at block 9000, that always sent an ancient block causing
// thousands of redundant round-trips through known blocks.
vector<CInv> vInv; 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->PushMessage("inv", vInv);
pfrom->hashContinue = 0; pfrom->hashContinue = 0;
} }
@@ -3549,7 +3775,11 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
// Send the rest of the chain // Send the rest of the chain
if (pindex) if (pindex)
pindex = pindex->pnext; 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", printf("IBD-DIAG: getblocks request from peer %s: start=%d stop=%s limit=%d\n",
pfrom->addr.ToString().c_str(), (pindex ? pindex->nHeight : -1), pfrom->addr.ToString().c_str(), (pindex ? pindex->nHeight : -1),
hashStop.ToString().substr(0,20).c_str(), nLimit); hashStop.ToString().substr(0,20).c_str(), nLimit);
@@ -3775,7 +4005,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
if (IsInitialBlockDownload()) if (IsInitialBlockDownload())
{ {
static int nBlocksSinceRequest = 0; static int nBlocksSinceRequest = 0;
if (++nBlocksSinceRequest >= 1000) if (++nBlocksSinceRequest >= 5000)
{ {
nBlocksSinceRequest = 0; nBlocksSinceRequest = 0;
pfrom->pindexLastGetBlocksBegin = NULL; pfrom->pindexLastGetBlocksBegin = NULL;
@@ -4231,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) if (IsInitialBlockDownload() && !pto->fClient)
{ {
@@ -4241,8 +4471,8 @@ bool SendMessages(CNode* pto, bool fSendTrickle)
if (nBestHeight > nLastHeight) { if (nBestHeight > nLastHeight) {
nLastHeight = nBestHeight; nLastHeight = nBestHeight;
nLastBlockReceived = GetTime(); nLastBlockReceived = GetTime();
} else if (nLastBlockReceived > 0 && GetTime() - nLastBlockReceived > 2) { } else if (nLastBlockReceived > 0 && GetTime() - nLastBlockReceived > 10) {
if (GetTime() - nLastStallLog >= 10) { // log every 10s max 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", printf("IBD-DIAG: STALL at height %d for %ds, peer=%s askfor_queue=%d send_size=%d\n",
nBestHeight, (int)(GetTime() - nLastBlockReceived), nBestHeight, (int)(GetTime() - nLastBlockReceived),
pto->addr.ToString().c_str(), pto->addr.ToString().c_str(),
+1
View File
@@ -112,6 +112,7 @@ CBlockIndex* FindBlockByHeight(int nHeight);
bool ProcessMessages(CNode* pfrom); bool ProcessMessages(CNode* pfrom);
bool SendMessages(CNode* pto, bool fSendTrickle); bool SendMessages(CNode* pto, bool fSendTrickle);
bool LoadExternalBlockFile(FILE* fileIn); bool LoadExternalBlockFile(FILE* fileIn);
bool FastImportBlockFile();
bool CheckProofOfWork(uint256 hash, unsigned int nBits); bool CheckProofOfWork(uint256 hash, unsigned int nBits);
unsigned int GetNextTargetRequired(const CBlockIndex* pindexLast, bool fProofOfStake); unsigned int GetNextTargetRequired(const CBlockIndex* pindexLast, bool fProofOfStake);
+8
View File
@@ -111,6 +111,7 @@ OBJS= \
obj/miner.o \ obj/miner.o \
obj/main.o \ obj/main.o \
obj/net.o \ obj/net.o \
obj/bootstrap.o \
obj/net_bootstrap.o \ obj/net_bootstrap.o \
obj/protocol.o \ obj/protocol.o \
obj/trianglesrpc.o \ obj/trianglesrpc.o \
@@ -232,6 +233,13 @@ obj/tor_embedded.o: tor/tor_embedded.cpp
-e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \ -e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \
rm -f $(@:%.o=%.d) 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 obj/net_bootstrap.o: net_bootstrap.cpp
$(CXX) -c $(xCXXFLAGS) -MMD -MF $(@:%.o=%.d) -o $@ $< $(CXX) -c $(xCXXFLAGS) -MMD -MF $(@:%.o=%.d) -o $@ $<
@cp $(@:%.o=%.d) $(@:%.o=%.P); \ @cp $(@:%.o=%.d) $(@:%.o=%.P); \
+8
View File
@@ -146,6 +146,7 @@ OBJS= \
obj/miner.o \ obj/miner.o \
obj/main.o \ obj/main.o \
obj/net.o \ obj/net.o \
obj/bootstrap.o \
obj/net_bootstrap.o \ obj/net_bootstrap.o \
obj/protocol.o \ obj/protocol.o \
obj/trianglesrpc.o \ obj/trianglesrpc.o \
@@ -279,6 +280,13 @@ obj/tor_embedded.o: tor/tor_embedded.cpp
-e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \ -e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \
rm -f $(@:%.o=%.d) 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 obj/net_bootstrap.o: net_bootstrap.cpp
$(CXX) -c $(xCXXFLAGS) -MMD -MF $(@:%.o=%.d) -o $@ $< $(CXX) -c $(xCXXFLAGS) -MMD -MF $(@:%.o=%.d) -o $@ $<
@cp $(@:%.o=%.d) $(@:%.o=%.P); \ @cp $(@:%.o=%.d) $(@:%.o=%.P); \
+10
View File
@@ -558,6 +558,16 @@ void StakeMiner(CWallet *pwallet)
} }
} }
//
// 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 // Create new block
// //
+9 -2
View File
@@ -15,7 +15,7 @@ static const int64_t nClientStartupTime = GetTime();
ClientModel::ClientModel(OptionsModel *optionsModel, QObject *parent) : ClientModel::ClientModel(OptionsModel *optionsModel, QObject *parent) :
QObject(parent), optionsModel(optionsModel), QObject(parent), optionsModel(optionsModel),
cachedNumBlocks(0), cachedNumBlocksOfPeers(0), pollTimer(0) cachedNumBlocks(0), cachedNumBlocksOfPeers(0), cachedNumConnections(0), pollTimer(0)
{ {
numBlocksAtStartup = -1; numBlocksAtStartup = -1;
@@ -34,7 +34,14 @@ ClientModel::~ClientModel()
int ClientModel::getNumConnections() const 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 int ClientModel::getNumBlocks() const
+1
View File
@@ -49,6 +49,7 @@ private:
int cachedNumBlocks; int cachedNumBlocks;
int cachedNumBlocksOfPeers; int cachedNumBlocksOfPeers;
mutable int cachedNumConnections;
int numBlocksAtStartup; int numBlocksAtStartup;
+1 -1
View File
@@ -2,7 +2,7 @@
#define GUICONSTANTS_H #define GUICONSTANTS_H
/* Milliseconds between model updates */ /* Milliseconds between model updates */
static const int MODEL_UPDATE_DELAY = 500; static const int MODEL_UPDATE_DELAY = 2500;
/* AskPassphraseDialog -- Maximum passphrase length */ /* AskPassphraseDialog -- Maximum passphrase length */
static const int MAX_PASSPHRASE_SIZE = 1024; static const int MAX_PASSPHRASE_SIZE = 1024;
+47 -54
View File
@@ -11,6 +11,7 @@
#include <QMessageBox> #include <QMessageBox>
#include <QDialogButtonBox> #include <QDialogButtonBox>
#include <QProgressDialog> #include <QProgressDialog>
#include <QCheckBox>
#include <QApplication> #include <QApplication>
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
@@ -204,79 +205,71 @@ bool IntroDialog::pickDataDirectory()
return false; return false;
} }
// Offer bootstrap download once (tracks via QSettings so it only asks once) // Offer bootstrap download on each startup (unless user checked "don't ask again")
fs::path dataDirPath(dataDir.toStdString()); fs::path dataDirPath(dataDir.toStdString());
if (!settings.value("bootstrapOffered", false).toBool()) if (!settings.value("bootstrapDontAsk", false).toBool())
{ {
settings.setValue("bootstrapOffered", true); QMessageBox msgBox;
msgBox.setWindowTitle("Triangles");
int ret = QMessageBox::question(0, "Triangles", msgBox.setText(
"Would you like to download the latest blockchain snapshot?\n\n" "Would you like to download the latest blockchain snapshot?\n\n"
"This will download the blockchain data from the Triangles network " "This will download the blockchain data from the Triangles network "
"and replace any existing chain data in your data directory.\n\n" "and replace any existing chain data in your data directory.\n\n"
"Click Yes to download, or No to sync from the network.", "Click Yes to download, or No to sync from the network.");
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); 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) if (ret == QMessageBox::Yes)
{ {
std::string host = Bootstrap::DEFAULT_HOST; std::string host = Bootstrap::DEFAULT_HOST;
std::string strError; std::string strError;
std::vector<std::string> files;
// Fetch file list (try domain first, then fallback to IP) QProgressDialog progress("Downloading blockchain snapshot...", "Cancel",
if (!Bootstrap::FetchFileList(host, files, strError)) { 0, 100, 0);
host = Bootstrap::FALLBACK_HOST;
if (!Bootstrap::FetchFileList(host, files, strError)) {
QMessageBox::warning(0, "Triangles",
QString("Could not reach bootstrap server:\n%1\n\n"
"The wallet will sync from the network instead.")
.arg(QString::fromStdString(strError)));
return true;
}
}
// Show progress dialog
QProgressDialog progress("Downloading blockchain data...", "Cancel",
0, (int)files.size(), 0);
progress.setWindowTitle("Triangles - Bootstrap"); progress.setWindowTitle("Triangles - Bootstrap");
progress.setWindowModality(Qt::ApplicationModal); progress.setWindowModality(Qt::ApplicationModal);
progress.setMinimumDuration(0); progress.setMinimumDuration(0);
progress.setValue(0); progress.setValue(0);
bool failed = false; auto progressFn = [&progress](int64_t bytesDownloaded, int64_t totalBytes) {
for (int i = 0; i < (int)files.size(); i++) { if (totalBytes > 0) {
if (progress.wasCanceled()) int pct = (int)((bytesDownloaded * 100) / totalBytes);
break; progress.setValue(pct);
progress.setLabelText(
QString filename = QString::fromStdString(files[i]); QString("Downloading blockchain snapshot... %1 MB / %2 MB")
progress.setLabelText( .arg(bytesDownloaded / (1024*1024))
QString("Downloading %1 (%2 of %3)...") .arg(totalBytes / (1024*1024)));
.arg(filename).arg(i + 1).arg((int)files.size())); } else {
progress.setValue(i); progress.setLabelText(
QApplication::processEvents(); QString("Downloading blockchain snapshot... %1 MB")
.arg(bytesDownloaded / (1024*1024)));
// Create subdirectories if needed (e.g. txleveldb/)
fs::path destPath = dataDirPath / files[i];
fs::create_directories(destPath.parent_path());
// Download this file
std::string urlPath = std::string(Bootstrap::BASE_PATH) + files[i];
if (!Bootstrap::DownloadFile(host, urlPath, destPath,
[](int64_t, int64_t) {
QApplication::processEvents();
}, strError))
{
QMessageBox::warning(0, "Triangles",
QString("Download failed for %1:\n%2\n\n"
"The wallet will sync remaining data from the network.")
.arg(filename).arg(QString::fromStdString(strError)));
failed = true;
break;
} }
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 (!failed) if (!success) {
progress.setValue((int)files.size()); 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);
}
} }
} }
+2
View File
@@ -126,6 +126,8 @@ OverviewPage::~OverviewPage()
void OverviewPage::setBalance(qint64 balance, qint64 stake, qint64 unconfirmedBalance, qint64 immatureBalance) void OverviewPage::setBalance(qint64 balance, qint64 stake, qint64 unconfirmedBalance, qint64 immatureBalance)
{ {
if (!model || !model->getOptionsModel())
return;
int unit = model->getOptionsModel()->getDisplayUnit(); int unit = model->getOptionsModel()->getDisplayUnit();
currentBalance = balance; currentBalance = balance;
currentStake = stake; currentStake = stake;
+42 -24
View File
@@ -799,19 +799,23 @@ void TrianglesGUI::setNumConnections(int count)
void TrianglesGUI::setNumBlocks(int count, int nTotalBlocks) 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)
if (!clientModel || clientModel->getNumConnections() == 0) 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); progressBarLabel->setVisible(false);
progressBar->setVisible(false); progressBar->setVisible(false);
ui->label_blocks->setVisible(false); ui->label_blocks->setVisible(false);
return;
} }
QString tooltip; QString tooltip;
if(count < nTotalBlocks) if(nConnections > 0 && count < nTotalBlocks)
{ {
// Calculate blocks/sec - only update rate when new blocks arrive // Calculate blocks/sec - only update rate when new blocks arrive
static int lastCount = 0; static int lastCount = 0;
@@ -899,20 +903,19 @@ void TrianglesGUI::setNumBlocks(int count, int nTotalBlocks)
text = tr("%n day(s) ago","",secs/(60*60*24)); text = tr("%n day(s) ago","",secs/(60*60*24));
} }
// Set icon state: spinning if catching up, tick otherwise // Set icon state: spinning if catching up, tick otherwise.
if(secs < 90*60 && count >= nTotalBlocks) // Use a generous threshold (6 hours) for PoS chains where block intervals
// can be long during difficulty adjustment with few stakers.
if(secs < 6*60*60 && count >= nTotalBlocks)
{ {
tooltip = tr("Up to date") + QString(".<br>") + tooltip; tooltip = tr("Up to date") + QString(".<br>") + tooltip;
labelBlocksIcon->setPixmap(QIcon(":/icons/synced").pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE)); labelBlocksIcon->setPixmap(QIcon(":/icons/synced").pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE));
overviewPage->showOutOfSyncWarning(false); overviewPage->showOutOfSyncWarning(false);
} }
else else
{ {
tooltip = tr("Catching up...") + QString("<br>") + tooltip; 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)); labelBlocksIcon->setPixmap(QIcon(":/icons/notsynced").pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE));
overviewPage->showOutOfSyncWarning(true); overviewPage->showOutOfSyncWarning(true);
@@ -1590,32 +1593,47 @@ void TrianglesGUI::toggleHidden()
void TrianglesGUI::updateStakingIcon() 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) 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) if (nLastCoinStakeSearchInterval && nWeight)
{ {
uint64_t nNetworkWeight = GetPoSKernelPS(); uint64_t nNetworkWeight = GetPoSKernelPS();
unsigned nEstimateTime = nTargetSpacing * nNetworkWeight / nWeight; unsigned nEstimateTime = nWeight > 0 ? nTargetSpacing * nNetworkWeight / nWeight : 0;
QString text; QString text;
if (nEstimateTime < 60) if (nEstimateTime < 60)
{
text = tr("%n second(s)", "", nEstimateTime); text = tr("%n second(s)", "", nEstimateTime);
}
else if (nEstimateTime < 60*60) else if (nEstimateTime < 60*60)
{
text = tr("%n minute(s)", "", nEstimateTime/60); text = tr("%n minute(s)", "", nEstimateTime/60);
}
else if (nEstimateTime < 24*60*60) else if (nEstimateTime < 24*60*60)
{
text = tr("%n hour(s)", "", nEstimateTime/(60*60)); text = tr("%n hour(s)", "", nEstimateTime/(60*60));
}
else else
{
text = tr("%n day(s)", "", nEstimateTime/(60*60*24)); text = tr("%n day(s)", "", nEstimateTime/(60*60*24));
}
labelStakingIcon->setPixmap(QIcon(":/icons/staking_on").pixmap(STATUSBAR_ICONSIZE,STATUSBAR_ICONSIZE)); 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)); 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 +1641,9 @@ void TrianglesGUI::updateStakingIcon()
else else
{ {
labelStakingIcon->setPixmap(QIcon(":/icons/staking_off").pixmap(STATUSBAR_ICONSIZE,STATUSBAR_ICONSIZE)); 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")); 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")); labelStakingIcon->setToolTip(tr("Not staking because wallet is offline"));
else if (IsInitialBlockDownload()) else if (IsInitialBlockDownload())
labelStakingIcon->setToolTip(tr("Not staking because wallet is syncing")); labelStakingIcon->setToolTip(tr("Not staking because wallet is syncing"));
+4 -4
View File
@@ -92,10 +92,10 @@ void WalletModel::pollBalanceChanged()
void WalletModel::checkBalanceChanged() void WalletModel::checkBalanceChanged()
{ {
qint64 newBalance = getBalance(); // Get all balances in a single lock acquisition + single pass
qint64 newStake = getStake(); // instead of 4 separate lock+iterate cycles
qint64 newUnconfirmedBalance = getUnconfirmedBalance(); qint64 newBalance = 0, newStake = 0, newUnconfirmedBalance = 0, newImmatureBalance = 0;
qint64 newImmatureBalance = getImmatureBalance(); wallet->GetAllBalances(newBalance, newStake, newUnconfirmedBalance, newImmatureBalance);
if(cachedBalance != newBalance || cachedStake != newStake || cachedUnconfirmedBalance != newUnconfirmedBalance || cachedImmatureBalance != newImmatureBalance) if(cachedBalance != newBalance || cachedStake != newStake || cachedUnconfirmedBalance != newUnconfirmedBalance || cachedImmatureBalance != newImmatureBalance)
{ {
+1 -1
View File
@@ -53,7 +53,7 @@ static const int MEMPOOL_GD_VERSION = 60002;
#define DISPLAY_VERSION_MAJOR 5 #define DISPLAY_VERSION_MAJOR 5
#define DISPLAY_VERSION_MINOR 3 #define DISPLAY_VERSION_MINOR 3
#define DISPLAY_VERSION_REVISION 3 #define DISPLAY_VERSION_REVISION 4
#define DISPLAY_VERSION_BUILD 0 #define DISPLAY_VERSION_BUILD 0
#endif #endif
+25
View File
@@ -1476,6 +1476,31 @@ int64_t CWallet::GetNewMint() const
return nTotal; return nTotal;
} }
void CWallet::GetAllBalances(int64_t& nBalance, int64_t& nStake, int64_t& nUnconfirmed, int64_t& nImmature) const
{
nBalance = 0;
nStake = 0;
nUnconfirmed = 0;
nImmature = 0;
LOCK(cs_wallet);
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();
}
}
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 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(); setCoinsRet.clear();
+11
View File
@@ -107,6 +107,8 @@ public:
nMasterKeyMaxID = 0; nMasterKeyMaxID = 0;
pwalletdbEncryption = NULL; pwalletdbEncryption = NULL;
nOrderPosNext = 0; nOrderPosNext = 0;
nCachedStakeWeight = 0;
nCachedStakeWeightTime = 0;
} }
CWallet(std::string strWalletFileIn) CWallet(std::string strWalletFileIn)
{ {
@@ -117,6 +119,8 @@ public:
nMasterKeyMaxID = 0; nMasterKeyMaxID = 0;
pwalletdbEncryption = NULL; pwalletdbEncryption = NULL;
nOrderPosNext = 0; nOrderPosNext = 0;
nCachedStakeWeight = 0;
nCachedStakeWeightTime = 0;
} }
std::map<uint256, CWalletTx> mapWallet; std::map<uint256, CWalletTx> mapWallet;
@@ -190,6 +194,8 @@ public:
int64_t GetImmatureBalance() const; int64_t GetImmatureBalance() const;
int64_t GetStake() const; int64_t GetStake() const;
int64_t GetNewMint() const; int64_t GetNewMint() const;
// Get all balances in a single lock acquisition + single pass (avoids 4x lock + 4x iteration)
void 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(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 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); bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey);
@@ -197,6 +203,11 @@ public:
bool GetStakeWeight(const CKeyStore& keystore, uint64_t& nMinWeight, uint64_t& nMaxWeight, uint64_t& nWeight); 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); 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.
// Access is safe without locks: written atomically by the miner, read by UI for display only.
volatile uint64_t nCachedStakeWeight;
volatile 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 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); std::string SendMoneyToDestination(const CTxDestination& address, int64_t nValue, std::string& sNarr, CWalletTx& wtxNew, bool fAskFee=false);