Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e3aeff002c | |||
| 09ccd4339c | |||
| 03aa38f1b4 | |||
| 9389a883f1 | |||
| 31fa26f03a | |||
| ce96d278cd | |||
| 7166b76bad | |||
| 540db0e210 | |||
| 59b75476ca | |||
| 0029b34698 | |||
| 8e03e89764 | |||
| 3b1850af9d |
@@ -2,7 +2,7 @@ name: Build All Platforms
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
branches: [master, cpp20-modernization]
|
||||
tags: ['v*']
|
||||
pull_request:
|
||||
branches: [master]
|
||||
@@ -56,6 +56,8 @@ jobs:
|
||||
SAN_FLAGS: "-fsanitize=address,undefined -fno-omit-frame-pointer -fno-sanitize-recover=undefined -fno-sanitize=alignment,signed-integer-overflow,vptr"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ if(POLICY CMP0167)
|
||||
endif()
|
||||
|
||||
project(Triangles
|
||||
VERSION 5.9.5
|
||||
VERSION 6.0.0
|
||||
DESCRIPTION "Cryptographic Triangles Wallet"
|
||||
LANGUAGES C CXX
|
||||
)
|
||||
|
||||
@@ -62,6 +62,8 @@ set(CORE_SOURCES
|
||||
pbkdf2.cpp
|
||||
scrypt.cpp
|
||||
smessage.cpp
|
||||
syncmanager.cpp
|
||||
chaindb_migrate.cpp
|
||||
tor_embed_hooks.cpp
|
||||
rest.cpp
|
||||
trianglesrpc.cpp
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
// Copyright (c) 2026 The Triangles developers.
|
||||
// Distributed under the MIT/X11 software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include "chaindb_migrate.h"
|
||||
|
||||
#include "txdb-leveldb.h"
|
||||
#include "txdb-rocksdb.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace {
|
||||
|
||||
struct ChainDbStats
|
||||
{
|
||||
int64_t nRecords = 0;
|
||||
int64_t nUtxos = 0;
|
||||
int64_t nUtxoValue = 0;
|
||||
uint256 hashBestChain = 0;
|
||||
int nDbFormat = 0;
|
||||
};
|
||||
|
||||
bool CollectStats(CTxDBBase& db, ChainDbStats& stats, std::string& strError)
|
||||
{
|
||||
stats = ChainDbStats();
|
||||
|
||||
auto it = db.NewIterator();
|
||||
for (it->Seek(std::string()); it->Valid(); it->Next())
|
||||
stats.nRecords++;
|
||||
|
||||
int nUtxos = 0;
|
||||
stats.nUtxoValue = db.SumUtxoValues(nUtxos);
|
||||
stats.nUtxos = nUtxos;
|
||||
db.ReadHashBestChain(stats.hashBestChain);
|
||||
db.ReadDbFormat(stats.nDbFormat);
|
||||
|
||||
if (stats.nRecords <= 0) {
|
||||
strError = "source chain database contains no records";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StatsMatch(const ChainDbStats& src, const ChainDbStats& dst, std::string& strError)
|
||||
{
|
||||
if (src.nRecords != dst.nRecords) {
|
||||
strError = strprintf("record count mismatch after migration: source=%lld rocksdb=%lld",
|
||||
(long long)src.nRecords, (long long)dst.nRecords);
|
||||
return false;
|
||||
}
|
||||
if (src.nUtxos != dst.nUtxos || src.nUtxoValue != dst.nUtxoValue) {
|
||||
strError = strprintf("UTXO mismatch after migration: source=(%lld,%lld) rocksdb=(%lld,%lld)",
|
||||
(long long)src.nUtxos, (long long)src.nUtxoValue,
|
||||
(long long)dst.nUtxos, (long long)dst.nUtxoValue);
|
||||
return false;
|
||||
}
|
||||
if (src.hashBestChain != dst.hashBestChain) {
|
||||
strError = strprintf("best-chain hash mismatch after migration: source=%s rocksdb=%s",
|
||||
src.hashBestChain.ToString().c_str(),
|
||||
dst.hashBestChain.ToString().c_str());
|
||||
return false;
|
||||
}
|
||||
if (src.nDbFormat != dst.nDbFormat) {
|
||||
strError = strprintf("dbformat mismatch after migration: source=%d rocksdb=%d",
|
||||
src.nDbFormat, dst.nDbFormat);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool MaybeMigrateLevelDbToRocksDb(bool fForce, std::string& strError)
|
||||
{
|
||||
strError.clear();
|
||||
|
||||
const fs::path dataDir = GetDataDir();
|
||||
const fs::path levelPath = dataDir / "txleveldb";
|
||||
const fs::path rocksPath = dataDir / "rocksdb";
|
||||
const fs::path markerPath = rocksPath / "MIGRATION_INCOMPLETE";
|
||||
|
||||
if (!fs::exists(levelPath))
|
||||
return true;
|
||||
|
||||
if (fs::exists(rocksPath)) {
|
||||
if (fs::exists(markerPath)) {
|
||||
printf("ChainDB migration: removing incomplete previous RocksDB migration\n");
|
||||
fs::remove_all(rocksPath);
|
||||
}
|
||||
else if (!fForce)
|
||||
return true;
|
||||
else {
|
||||
printf("ChainDB migration: removing existing RocksDB directory due to -migratechaindbforce\n");
|
||||
fs::remove_all(rocksPath);
|
||||
}
|
||||
}
|
||||
|
||||
printf("ChainDB migration: copying LevelDB chain state to RocksDB...\n");
|
||||
printf("ChainDB migration: source=%s destination=%s\n",
|
||||
levelPath.string().c_str(), rocksPath.string().c_str());
|
||||
|
||||
try {
|
||||
fs::create_directories(rocksPath);
|
||||
{
|
||||
std::ofstream marker(markerPath);
|
||||
marker << "RocksDB migration in progress. Safe to delete this directory and retry.\n";
|
||||
}
|
||||
|
||||
CTxDB source("r");
|
||||
CRocksTxDB destination("c+");
|
||||
|
||||
ChainDbStats srcStats;
|
||||
if (!CollectStats(source, srcStats, strError)) {
|
||||
source.Close();
|
||||
destination.Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!destination.TxnBegin()) {
|
||||
strError = "failed to begin RocksDB migration batch";
|
||||
source.Close();
|
||||
destination.Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
int64_t nCopied = 0;
|
||||
auto it = source.NewIterator();
|
||||
for (it->Seek(std::string()); it->Valid(); it->Next())
|
||||
{
|
||||
if (!destination.WriteRawRecordForMigration(it->KeyStr(), it->ValueStr())) {
|
||||
destination.TxnAbort();
|
||||
strError = "failed to write migrated record to RocksDB";
|
||||
source.Close();
|
||||
destination.Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (++nCopied % 100000 == 0)
|
||||
{
|
||||
if (!destination.TxnCommit()) {
|
||||
strError = "failed to commit RocksDB migration batch";
|
||||
source.Close();
|
||||
destination.Close();
|
||||
return false;
|
||||
}
|
||||
printf("ChainDB migration: copied %lld / %lld records\n",
|
||||
(long long)nCopied, (long long)srcStats.nRecords);
|
||||
if (!destination.TxnBegin()) {
|
||||
strError = "failed to begin RocksDB migration batch";
|
||||
source.Close();
|
||||
destination.Close();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!destination.TxnCommit()) {
|
||||
strError = "failed to commit final RocksDB migration batch";
|
||||
source.Close();
|
||||
destination.Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
ChainDbStats dstStats;
|
||||
if (!CollectStats(destination, dstStats, strError)) {
|
||||
source.Close();
|
||||
destination.Close();
|
||||
return false;
|
||||
}
|
||||
if (!StatsMatch(srcStats, dstStats, strError)) {
|
||||
source.Close();
|
||||
destination.Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
printf("ChainDB migration: verified %lld records, %lld UTXOs, best=%s\n",
|
||||
(long long)dstStats.nRecords,
|
||||
(long long)dstStats.nUtxos,
|
||||
dstStats.hashBestChain.ToString().substr(0,20).c_str());
|
||||
|
||||
source.Close();
|
||||
destination.Close();
|
||||
fs::remove(markerPath);
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
strError = e.what();
|
||||
return false;
|
||||
}
|
||||
|
||||
printf("ChainDB migration: complete. Legacy LevelDB was left untouched at %s\n",
|
||||
levelPath.string().c_str());
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) 2026 The Triangles developers.
|
||||
// Distributed under the MIT/X11 software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
#ifndef TRIANGLES_CHAINDB_MIGRATE_H
|
||||
#define TRIANGLES_CHAINDB_MIGRATE_H
|
||||
|
||||
#include <string>
|
||||
|
||||
// Migrate legacy LevelDB chain state from <datadir>/txleveldb to RocksDB in
|
||||
// <datadir>/rocksdb. The source is never modified. Returns true when migration
|
||||
// succeeds or when there is nothing to migrate.
|
||||
bool MaybeMigrateLevelDbToRocksDb(bool fForce, std::string& strError);
|
||||
|
||||
#endif // TRIANGLES_CHAINDB_MIGRATE_H
|
||||
+2
-20
@@ -25,21 +25,13 @@ namespace Checkpoints
|
||||
{ 2101, uint256("0xd4ea1ac45b63c8162a7fc8033cec441db8d532ba988202849d7831e32fe2d059")},
|
||||
{ 2847, uint256("0xb5015e2835f13fd3bb6135cff9b31ac33310c9b77d694bdb592b8680d98d018e")},
|
||||
{ 3589, uint256("0xb12a2ca3db4e288cada98aa2139768532bd4474c49dd7b8158031032dac08d51")},
|
||||
{ 3935, uint256("0xe16290c9757d1368b8d7c35de07d4f8f70c2c9f9c785b667df0c3bff85086ca6")},
|
||||
{ 3935, uint256("0xe16290c9757d1368b8d7c35de4f8f70c2c9f9c785b667df0c3bff85086ca6")},
|
||||
{ 5703, uint256("0x587db07bb2172ad7db72c5fabc2518262a1b27f503f99417510b2c6fafa6557b")},
|
||||
{ 9000, uint256("0x00000000019ef6b2f5e7c324c7d083ee94502305aabc7e9cd73a7fb2a57bb8db")},
|
||||
{ 9001, uint256("0x6d5c6c5f201cc9e59659ee0da30d1430dc6bf3b12a8ff4c3864ab8d6286b0007")},
|
||||
{ 9002, uint256("0xa1e20fb1d44688b763690cf74d6aefe859e4cc32981f9e3f2b2ae9702bbcf249")},
|
||||
{ 10881, uint256("0x4b6554c45e1e6764a6f3c309c47baf53c9edd81f624e52b072518cd15da237e6")},
|
||||
{ 17650, uint256("0x224940e1f986a202209b8e762728d1452ab45870c308abf84905674acf326a47")},
|
||||
{2000000, uint256("0xb0b02d4bb5ffa31f6f22fd042082ca0a085257af34dc2d4b31c3c8567b5574d5")},
|
||||
{2186940, uint256("0xbd952e8d4a612e336d840ad924a7e09395e36bcd9d929b302e47e60b5c3098c0")},
|
||||
{2190000, uint256("0x682baf783581468ba18f9967254a7f3944e8b8c4cc7101e7d99b68f4f9dd5271")},
|
||||
{2200000, uint256("0x0a8d0442f031f1258120f713f34e45f4f9a625fb753558e27b89b32ad5a9a740")},
|
||||
{2205000, uint256("0xf7aa893ec012181e321783d6a5487addf6997377908faa3e760c9054a4217d29")},
|
||||
{2206000, uint256("0x780ae878f8b10b6cbd51ceb2c0799c90d3551fa916be62974375071b9e36581c")},
|
||||
{2203594, uint256("0x5e016ae5d1f163c6679292b717a3db467a39d24b0a315182f4783caa79c722d8")},
|
||||
{2207000, uint256("0x8836d67b0f08036c4a7c26ff0a29d4461a52b6d8f552165ad9c1abec2f3cadfd")},
|
||||
};
|
||||
|
||||
// Published UTXO snapshot file SHA256, keyed by snapshot height.
|
||||
@@ -51,7 +43,6 @@ namespace Checkpoints
|
||||
// here. The corresponding (height, blockhash) must already exist in
|
||||
// mapCheckpoints / mapCheckpointsTestnet.
|
||||
static std::map<int, uint256> mapSnapshotHashes = {
|
||||
{2203594, uint256("0x49b35dd01659975c4a31954f37174c6e2e8878dd0723ab306ccecd991c80f79a")},
|
||||
};
|
||||
|
||||
static std::map<int, uint256> mapSnapshotHashesTestnet = {
|
||||
@@ -63,22 +54,13 @@ namespace Checkpoints
|
||||
{ 2101, uint256("0xd4ea1ac45b63c8162a7fc8033cec441db8d532ba988202849d7831e32fe2d059")},
|
||||
{ 2847, uint256("0xb5015e2835f13fd3bb6135cff9b31ac33310c9b77d694bdb592b8680d98d018e")},
|
||||
{ 3589, uint256("0xb12a2ca3db4e288cada98aa2139768532bd4474c49dd7b8158031032dac08d51")},
|
||||
{ 3935, uint256("0xe16290c9757d1368b8d7c35de07d4f8f70c2c9f9c785b667df0c3bff85086ca6")},
|
||||
{ 3935, uint256("0xe16290c9757d1368b8d7c35de4f8f70c2c9f9c785b667df0c3bff85086ca6")},
|
||||
{ 5703, uint256("0x587db07bb2172ad7db72c5fabc2518262a1b27f503f99417510b2c6fafa6557b")},
|
||||
{ 9000, uint256("0x00000000019ef6b2f5e7c324c7d083ee94502305aabc7e9cd73a7fb2a57bb8db")},
|
||||
{ 9001, uint256("0x6d5c6c5f201cc9e59659ee0da30d1430dc6bf3b12a8ff4c3864ab8d6286b0007")},
|
||||
{ 9002, uint256("0xa1e20fb1d44688b763690cf74d6aefe859e4cc32981f9e3f2b2ae9702bbcf249")},
|
||||
{ 10881, uint256("0x4b6554c45e1e6764a6f3c309c47baf53c9edd81f624e52b072518cd15da237e6")},
|
||||
{ 17650, uint256("0x224940e1f986a202209b8e762728d1452ab45870c308abf84905674acf326a47")},
|
||||
{2000000, uint256("0xb0b02d4bb5ffa31f6f22fd042082ca0a085257af34dc2d4b31c3c8567b5574d5")},
|
||||
{2186940, uint256("0xbd952e8d4a612e336d840ad924a7e09395e36bcd9d929b302e47e60b5c3098c0")},
|
||||
{2190000, uint256("0x682baf783581468ba18f9967254a7f3944e8b8c4cc7101e7d99b68f4f9dd5271")},
|
||||
{2200000, uint256("0x0a8d0442f031f1258120f713f34e45f4f9a625fb753558e27b89b32ad5a9a740")},
|
||||
{2205000, uint256("0xf7aa893ec012181e321783d6a5487addf6997377908faa3e760c9054a4217d29")},
|
||||
{2206000, uint256("0x780ae878f8b10b6cbd51ceb2c0799c90d3551fa916be62974375071b9e36581c")},
|
||||
{2207000, uint256("0x8836d67b0f08036c4a7c26ff0a29d4461a52b6d8f552165ad9c1abec2f3cadfd")},
|
||||
{2208000, uint256("0xe4a19e8a29fa7aae47f7563377af3e896fee18ed069a64e325b8c2c6c820a1be")},
|
||||
{2209000, uint256("0x04c78a6fc863bed918a9364c58c64489943b2e85d84ddb1ac2fba584f390d5dc")},
|
||||
};
|
||||
|
||||
bool CheckHardened(int nHeight, const uint256& hash)
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
// These need to be macros, as version.cpp's and triangles-qt.rc's voodoo requires it
|
||||
#define CLIENT_VERSION_MAJOR 5
|
||||
#define CLIENT_VERSION_MINOR 9
|
||||
#define CLIENT_VERSION_REVISION 5
|
||||
#define CLIENT_VERSION_REVISION 9
|
||||
#define CLIENT_VERSION_BUILD 0
|
||||
|
||||
// Converts the parameter X to a string after macro replacement on X has been performed.
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#endif
|
||||
#include "notificationqueue.h"
|
||||
#include "addressindex.h"
|
||||
#include "chaindb_migrate.h"
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
@@ -1022,6 +1023,16 @@ bool AppInit2()
|
||||
}
|
||||
}
|
||||
|
||||
// ********************************************************* Step 6d: optional LevelDB -> RocksDB chain DB migration
|
||||
if (GetBoolArg("-migratechaindb", false) || GetBoolArg("-migratechaindbforce", false))
|
||||
{
|
||||
uiInterface.InitMessage(_("Migrating chain database to RocksDB..."));
|
||||
std::string strMigrateError;
|
||||
bool fForce = GetBoolArg("-migratechaindbforce", false);
|
||||
if (!MaybeMigrateLevelDbToRocksDb(fForce, strMigrateError))
|
||||
return InitError(strprintf(_("Chain DB migration failed: %s"), strMigrateError.c_str()));
|
||||
}
|
||||
|
||||
// ********************************************************* Step 7: load blockchain
|
||||
|
||||
if (!bitdb.Open(GetDataDir()))
|
||||
|
||||
+40
-729
@@ -21,6 +21,7 @@
|
||||
#include "notificationqueue.h"
|
||||
#include "addressindex.h"
|
||||
#include "snapshotnet.h"
|
||||
#include "syncmanager.h"
|
||||
#include <algorithm>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
@@ -118,36 +119,9 @@ extern enum Checkpoints::CPMode CheckpointsMode;
|
||||
namespace
|
||||
{
|
||||
|
||||
struct CHeaderSyncNode
|
||||
{
|
||||
CBlock header;
|
||||
int nHeight;
|
||||
uint256 nChainTrust;
|
||||
bool fRequested;
|
||||
int64_t nLastRequestTime;
|
||||
int64_t nFirstRequestTime; // when this block was first requested (for latency tracking)
|
||||
int64_t nInsertTime;
|
||||
};
|
||||
|
||||
static std::map<uint256, CHeaderSyncNode> mapHeaderSync;
|
||||
static uint256 hashBestHeaderSync = 0;
|
||||
static int64_t nLastNewHeaderTime = 0;
|
||||
static CCriticalSection cs_PostIbdWork;
|
||||
static bool fPostIbdWorkStarted = false;
|
||||
|
||||
static const unsigned int MAX_HEADER_SYNC_CACHE = 15000;
|
||||
static const unsigned int HEADER_DOWNLOAD_WINDOW = 1024; // Wider pipeline for multi-peer parallel IBD
|
||||
static const unsigned int HEADER_DOWNLOAD_PER_PEER = 32; // Reduced from 64 for Tor circuit stability
|
||||
static const size_t HEADER_REDUNDANT_PEER_THRESHOLD = 4; // Only do dual-peer redundancy when peer count is below this
|
||||
static const unsigned int HEADER_SYNC_LOW_WATER = HEADER_DOWNLOAD_WINDOW / 4;
|
||||
static const unsigned int HEADER_SYNC_TARGET_INFLIGHT = HEADER_DOWNLOAD_WINDOW / 2;
|
||||
static const int64_t HEADER_REQUEST_TIMEOUT_MICROS = 60 * 1000000; // 60s for Tor latency (was 30s)
|
||||
static const int64_t HEADER_REDUNDANT_REQUEST_MICROS = 5 * 1000000; // 5s redundant request (reduced for Tor)
|
||||
static const int64_t HEADER_SYNC_TTL_MICROS = 15 * 60 * 1000000; // 15-minute TTL for cache entries (extended for Tor latency)
|
||||
static const int64_t HEADER_SYNC_REFILL_MIN_INTERVAL_SECONDS = 5;
|
||||
static const int64_t HEADER_SYNC_CONTROL_INTERVAL_SECONDS = 5;
|
||||
static const int64_t HEADER_SYNC_WATCHDOG_SECONDS = 25;
|
||||
|
||||
static void ThreadPostIbdWork(void* parg)
|
||||
{
|
||||
RenameThread("Triangles-postibd");
|
||||
@@ -195,499 +169,6 @@ static void ThreadPostIbdWork(void* parg)
|
||||
}
|
||||
}
|
||||
|
||||
static uint256 GetHeaderSyncTrust(unsigned int nBits)
|
||||
{
|
||||
CBigNum bnTarget;
|
||||
bnTarget.SetCompact(nBits);
|
||||
|
||||
if (bnTarget <= 0)
|
||||
return 0;
|
||||
|
||||
return ((CBigNum(1) << 256) / (bnTarget + 1)).getuint256();
|
||||
}
|
||||
|
||||
static bool GetKnownHeaderState(const uint256& hash, int& nHeight, uint256& nChainTrust)
|
||||
{
|
||||
if (auto miBlock = mapBlockIndex.find(hash); miBlock != mapBlockIndex.end())
|
||||
{
|
||||
nHeight = miBlock->second->nHeight;
|
||||
nChainTrust = miBlock->second->nChainTrust;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (auto miHeader = mapHeaderSync.find(hash); miHeader != mapHeaderSync.end())
|
||||
{
|
||||
nHeight = miHeader->second.nHeight;
|
||||
nChainTrust = miHeader->second.nChainTrust;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool GetHeaderSyncPrevHash(const uint256& hash, uint256& hashPrev)
|
||||
{
|
||||
if (auto miHeader = mapHeaderSync.find(hash); miHeader != mapHeaderSync.end())
|
||||
{
|
||||
hashPrev = miHeader->second.header.hashPrevBlock;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (auto miBlock = mapBlockIndex.find(hash); miBlock != mapBlockIndex.end() && miBlock->second->pprev)
|
||||
{
|
||||
hashPrev = miBlock->second->pprev->GetBlockHash();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void RecomputeBestHeaderSync()
|
||||
{
|
||||
hashBestHeaderSync = 0;
|
||||
uint256 nBestTrust = 0;
|
||||
|
||||
for (const auto& [hash, node] : mapHeaderSync)
|
||||
{
|
||||
if (hashBestHeaderSync == 0 || node.nChainTrust > nBestTrust)
|
||||
{
|
||||
hashBestHeaderSync = hash;
|
||||
nBestTrust = node.nChainTrust;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void PruneHeaderSync()
|
||||
{
|
||||
const int64_t nNow = GetTime() * 1000000;
|
||||
|
||||
// TTL eviction: remove entries older than 5 minutes
|
||||
if (mapHeaderSync.size() > MAX_HEADER_SYNC_CACHE / 2)
|
||||
{
|
||||
unsigned int nEvicted = 0;
|
||||
for (auto it = mapHeaderSync.begin(); it != mapHeaderSync.end(); )
|
||||
{
|
||||
if (nNow - it->second.nInsertTime >= HEADER_SYNC_TTL_MICROS)
|
||||
{
|
||||
it = mapHeaderSync.erase(it);
|
||||
++nEvicted;
|
||||
}
|
||||
else
|
||||
++it;
|
||||
}
|
||||
if (nEvicted > 0)
|
||||
{
|
||||
printf("IBD-DIAG: TTL-evicted %u stale header sync entries, %u remain\n",
|
||||
nEvicted, (unsigned int)mapHeaderSync.size());
|
||||
RecomputeBestHeaderSync();
|
||||
}
|
||||
}
|
||||
|
||||
// Hard limit: if still over max, evict oldest entries
|
||||
if (mapHeaderSync.size() > MAX_HEADER_SYNC_CACHE)
|
||||
{
|
||||
printf("IBD-DIAG: header sync cache exceeded %u entries, evicting oldest\n", MAX_HEADER_SYNC_CACHE);
|
||||
while (mapHeaderSync.size() > MAX_HEADER_SYNC_CACHE * 3 / 4)
|
||||
{
|
||||
// Find oldest entry by insert time
|
||||
auto oldest = mapHeaderSync.begin();
|
||||
for (auto it = mapHeaderSync.begin(); it != mapHeaderSync.end(); ++it)
|
||||
{
|
||||
if (it->second.nInsertTime < oldest->second.nInsertTime)
|
||||
oldest = it;
|
||||
}
|
||||
mapHeaderSync.erase(oldest);
|
||||
}
|
||||
RecomputeBestHeaderSync();
|
||||
}
|
||||
}
|
||||
|
||||
static bool AddHeaderSyncNode(const CBlock& header, const uint256& hashHeader)
|
||||
{
|
||||
if (mapBlockIndex.count(hashHeader) || mapHeaderSync.count(hashHeader))
|
||||
return true;
|
||||
|
||||
if (!header.vtx.empty())
|
||||
{
|
||||
printf("IBD-DIAG: header rejected (has vtx) hash=%s\n", hashHeader.ToString().substr(0,20).c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (header.GetBlockTime() > GetTime() + 15 * 60)
|
||||
{
|
||||
printf("IBD-DIAG: header rejected (future time) hash=%s time=%u\n",
|
||||
hashHeader.ToString().substr(0,20).c_str(), header.nTime);
|
||||
return false;
|
||||
}
|
||||
|
||||
int nPrevHeight = -1;
|
||||
uint256 nPrevChainTrust = 0;
|
||||
if (!GetKnownHeaderState(header.hashPrevBlock, nPrevHeight, nPrevChainTrust))
|
||||
{
|
||||
printf("IBD-DIAG: header rejected (prev unknown) hash=%s prevHash=%s\n",
|
||||
hashHeader.ToString().substr(0,20).c_str(),
|
||||
header.hashPrevBlock.ToString().substr(0,20).c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
const int nHeight = nPrevHeight + 1;
|
||||
if (nHeight <= CUTOFF_POW_BLOCK && !CheckProofOfWork(hashHeader, header.nBits))
|
||||
{
|
||||
printf("IBD-DIAG: header PoW FAILED at height %d hash=%s nBits=%08x prevHash=%s\n",
|
||||
nHeight, hashHeader.ToString().substr(0,20).c_str(), header.nBits,
|
||||
header.hashPrevBlock.ToString().substr(0,20).c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
CHeaderSyncNode node;
|
||||
node.header = header;
|
||||
node.nHeight = nHeight;
|
||||
node.nChainTrust = nPrevChainTrust + GetHeaderSyncTrust(header.nBits);
|
||||
node.fRequested = false;
|
||||
node.nLastRequestTime = 0;
|
||||
node.nFirstRequestTime = 0;
|
||||
node.nInsertTime = GetTime() * 1000000;
|
||||
|
||||
mapHeaderSync.insert({hashHeader, node});
|
||||
|
||||
if (hashBestHeaderSync == 0 || node.nChainTrust > mapHeaderSync[hashBestHeaderSync].nChainTrust)
|
||||
hashBestHeaderSync = hashHeader;
|
||||
|
||||
PruneHeaderSync();
|
||||
return true;
|
||||
}
|
||||
|
||||
static CBlockLocator BuildHeaderSyncLocator(uint256 hashTip)
|
||||
{
|
||||
if (hashTip == 0)
|
||||
return CBlockLocator(pindexBest);
|
||||
|
||||
std::vector<uint256> vHave;
|
||||
int nStep = 1;
|
||||
|
||||
while (hashTip != 0)
|
||||
{
|
||||
vHave.push_back(hashTip);
|
||||
|
||||
for (int i = 0; i < nStep && hashTip != 0; ++i)
|
||||
{
|
||||
uint256 hashPrev = 0;
|
||||
if (!GetHeaderSyncPrevHash(hashTip, hashPrev))
|
||||
hashTip = 0;
|
||||
else
|
||||
hashTip = hashPrev;
|
||||
}
|
||||
|
||||
if (vHave.size() > 10)
|
||||
nStep *= 2;
|
||||
}
|
||||
|
||||
vHave.push_back(!fTestNet ? hashGenesisBlockOfficial : hashGenesisBlockTestNet);
|
||||
return CBlockLocator(vHave);
|
||||
}
|
||||
|
||||
static std::vector<uint256> GetHeaderSyncDownloadPath(uint256 hashTip)
|
||||
{
|
||||
std::vector<uint256> vPath;
|
||||
|
||||
while (hashTip != 0 && !mapBlockIndex.count(hashTip))
|
||||
{
|
||||
auto mi = mapHeaderSync.find(hashTip);
|
||||
if (mi == mapHeaderSync.end())
|
||||
break;
|
||||
|
||||
vPath.push_back(hashTip);
|
||||
hashTip = mi->second.header.hashPrevBlock;
|
||||
}
|
||||
|
||||
std::reverse(vPath.begin(), vPath.end());
|
||||
return vPath;
|
||||
}
|
||||
|
||||
static unsigned int CountHeaderSyncInFlight()
|
||||
{
|
||||
const int64_t nNow = GetTime() * 1000000;
|
||||
unsigned int nInFlight = 0;
|
||||
for (const auto& [hash, node] : mapHeaderSync)
|
||||
{
|
||||
if (node.fRequested && nNow - node.nLastRequestTime < HEADER_REQUEST_TIMEOUT_MICROS)
|
||||
++nInFlight;
|
||||
}
|
||||
return nInFlight;
|
||||
}
|
||||
|
||||
static unsigned int GetHeaderSyncPlannerDepth()
|
||||
{
|
||||
if (hashBestHeaderSync == 0)
|
||||
return 0;
|
||||
|
||||
return (unsigned int)GetHeaderSyncDownloadPath(hashBestHeaderSync).size();
|
||||
}
|
||||
|
||||
static int GetHeaderSyncPlannerHeight()
|
||||
{
|
||||
if (hashBestHeaderSync == 0)
|
||||
return pindexBest ? pindexBest->nHeight : -1;
|
||||
|
||||
auto mi = mapHeaderSync.find(hashBestHeaderSync);
|
||||
if (mi == mapHeaderSync.end())
|
||||
return pindexBest ? pindexBest->nHeight : -1;
|
||||
|
||||
return mi->second.nHeight;
|
||||
}
|
||||
|
||||
static unsigned int QueueHeaderSyncBlocks(CNode* pfrom, unsigned int nWindow)
|
||||
{
|
||||
if (!pfrom || hashBestHeaderSync == 0)
|
||||
return 0;
|
||||
|
||||
const std::vector<uint256> vPath = GetHeaderSyncDownloadPath(hashBestHeaderSync);
|
||||
if (vPath.empty())
|
||||
return 0;
|
||||
|
||||
const int64_t nNow = GetTime() * 1000000;
|
||||
unsigned int nInFlight = CountHeaderSyncInFlight();
|
||||
unsigned int nQueued = 0;
|
||||
|
||||
for (const auto& hash : vPath)
|
||||
{
|
||||
if (nInFlight + nQueued >= nWindow)
|
||||
break;
|
||||
|
||||
auto mi = mapHeaderSync.find(hash);
|
||||
if (mi == mapHeaderSync.end())
|
||||
continue;
|
||||
|
||||
if (mi->second.fRequested && nNow - mi->second.nLastRequestTime < HEADER_REQUEST_TIMEOUT_MICROS)
|
||||
continue;
|
||||
|
||||
pfrom->AskFor(CInv(MSG_BLOCK, hash));
|
||||
mi->second.fRequested = true;
|
||||
mi->second.nLastRequestTime = nNow;
|
||||
++nQueued;
|
||||
}
|
||||
|
||||
return nQueued;
|
||||
}
|
||||
|
||||
// Returns the first request time (microseconds) for a block in the header sync cache, or 0
|
||||
static int64_t GetHeaderSyncRequestTime(const uint256& hashBlock)
|
||||
{
|
||||
auto mi = mapHeaderSync.find(hashBlock);
|
||||
if (mi == mapHeaderSync.end())
|
||||
return 0;
|
||||
return mi->second.nFirstRequestTime;
|
||||
}
|
||||
|
||||
static void MarkHeaderSyncBlockAccepted(const uint256& hashBlock)
|
||||
{
|
||||
auto mi = mapHeaderSync.find(hashBlock);
|
||||
if (mi == mapHeaderSync.end())
|
||||
return;
|
||||
|
||||
mapHeaderSync.erase(mi);
|
||||
if (hashBestHeaderSync == hashBlock)
|
||||
RecomputeBestHeaderSync();
|
||||
}
|
||||
|
||||
static void ContinueHeaderSync(CNode* pfrom, const uint256& hashTip)
|
||||
{
|
||||
if (!pfrom || hashTip == 0)
|
||||
return;
|
||||
|
||||
CBlockLocator locator = BuildHeaderSyncLocator(hashTip);
|
||||
if (locator.IsNull())
|
||||
return;
|
||||
|
||||
pfrom->PushMessage("getheaders", locator, uint256(0));
|
||||
}
|
||||
|
||||
static bool RequestHeaderSyncRefill(CNode* pfrom, uint256 hashTip, int64_t nMinIntervalSeconds, const char* pszReason)
|
||||
{
|
||||
if (!pfrom || pfrom->fClient || pfrom->nVersion == 0 || !IsInitialBlockDownload())
|
||||
return false;
|
||||
|
||||
const int64_t nNowSec = GetTime();
|
||||
if (nMinIntervalSeconds > 0 &&
|
||||
nNowSec - pfrom->nLastIbdHeaderRequest < nMinIntervalSeconds)
|
||||
return false;
|
||||
|
||||
uint256 hashLocatorTip = hashTip;
|
||||
if (hashLocatorTip == 0 ||
|
||||
(!mapBlockIndex.count(hashLocatorTip) && !mapHeaderSync.count(hashLocatorTip)))
|
||||
{
|
||||
hashLocatorTip = hashBestHeaderSync;
|
||||
}
|
||||
|
||||
if (hashLocatorTip != 0 && (!pindexBest || hashLocatorTip != pindexBest->GetBlockHash()))
|
||||
{
|
||||
ContinueHeaderSync(pfrom, hashLocatorTip);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!pindexBest)
|
||||
return false;
|
||||
|
||||
pfrom->pindexLastGetHeadersBegin = nullptr;
|
||||
pfrom->PushGetHeaders(pindexBest, uint256(0));
|
||||
hashLocatorTip = pindexBest->GetBlockHash();
|
||||
}
|
||||
|
||||
pfrom->nLastIbdHeaderRequest = nNowSec;
|
||||
printf("IBD-DIAG: %s getheaders to peer=%s locator=%s plannerDepth=%u inflight=%u\n",
|
||||
pszReason, pfrom->addr.ToString().c_str(),
|
||||
hashLocatorTip.ToString().substr(0,20).c_str(),
|
||||
GetHeaderSyncPlannerDepth(), CountHeaderSyncInFlight());
|
||||
return true;
|
||||
}
|
||||
|
||||
static unsigned int RequestHeaderSyncRefillAllPeers(uint256 hashTip, int64_t nMinIntervalSeconds, const char* pszReason)
|
||||
{
|
||||
std::vector<CNode*> vEligiblePeers;
|
||||
{
|
||||
LOCK(cs_vNodes);
|
||||
for (CNode* pnode : vNodes)
|
||||
{
|
||||
if (!pnode->fClient && pnode->nVersion != 0 && !pnode->fDisconnect)
|
||||
vEligiblePeers.push_back(pnode);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int nRequested = 0;
|
||||
for (CNode* pnode : vEligiblePeers)
|
||||
{
|
||||
if (RequestHeaderSyncRefill(pnode, hashTip, nMinIntervalSeconds, pszReason))
|
||||
++nRequested;
|
||||
}
|
||||
|
||||
return nRequested;
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
// Sort peers by blocks delivered (descending) for speed-weighted assignment.
|
||||
// Faster peers get more blocks assigned to them, improving IBD throughput
|
||||
// on Tor networks where latency varies significantly between peers.
|
||||
std::sort(vEligiblePeers.begin(), vEligiblePeers.end(),
|
||||
[](const CNode* a, const CNode* b) {
|
||||
return a->nBlocksDelivered > b->nBlocksDelivered;
|
||||
});
|
||||
|
||||
// Build a weighted distribution: top peer gets 3 slots per round, second gets 2, rest get 1.
|
||||
std::vector<CNode*> vWeightedPeers;
|
||||
for (size_t i = 0; i < vEligiblePeers.size(); i++)
|
||||
{
|
||||
int nWeight = (i == 0) ? 3 : (i == 1) ? 2 : 1;
|
||||
for (int w = 0; w < nWeight; w++)
|
||||
vWeightedPeers.push_back(vEligiblePeers[i]);
|
||||
}
|
||||
|
||||
// Adaptive timeout: use average peer latency to set timeouts.
|
||||
// If peers average 2s, timeout at 10s. If peers average 15s, timeout at 45s.
|
||||
// Clamp between 10s and 60s. Default to 60s when no latency data.
|
||||
int64_t nAdaptiveTimeout = HEADER_REQUEST_TIMEOUT_MICROS;
|
||||
{
|
||||
int64_t nTotalLatency = 0;
|
||||
int nPeersWithLatency = 0;
|
||||
for (const CNode* pnode : vEligiblePeers) {
|
||||
if (pnode->nAvgBlockLatencyUs > 0) {
|
||||
nTotalLatency += pnode->nAvgBlockLatencyUs;
|
||||
++nPeersWithLatency;
|
||||
}
|
||||
}
|
||||
if (nPeersWithLatency > 0) {
|
||||
int64_t nAvgLatency = nTotalLatency / nPeersWithLatency;
|
||||
nAdaptiveTimeout = std::max((int64_t)(10 * 1000000),
|
||||
std::min((int64_t)(60 * 1000000), nAvgLatency * 5));
|
||||
}
|
||||
}
|
||||
|
||||
// Distribute blocks across peers using speed-weighted assignment
|
||||
for (const auto& hash : vPath)
|
||||
{
|
||||
if (nInFlight + nQueued >= nWindow)
|
||||
break;
|
||||
|
||||
auto mi = mapHeaderSync.find(hash);
|
||||
if (mi == mapHeaderSync.end())
|
||||
continue;
|
||||
|
||||
bool fNeedsRequest = false;
|
||||
if (!mi->second.fRequested)
|
||||
{
|
||||
fNeedsRequest = true;
|
||||
}
|
||||
else if (nNow - mi->second.nLastRequestTime >= nAdaptiveTimeout)
|
||||
{
|
||||
fNeedsRequest = true;
|
||||
}
|
||||
else if (nNow - mi->second.nLastRequestTime >= HEADER_REDUNDANT_REQUEST_MICROS)
|
||||
{
|
||||
fNeedsRequest = true;
|
||||
}
|
||||
|
||||
if (!fNeedsRequest)
|
||||
continue;
|
||||
|
||||
CNode* pnode = vWeightedPeers[nPeerIndex % vWeightedPeers.size()];
|
||||
pnode->AskFor(CInv(MSG_BLOCK, hash));
|
||||
|
||||
if (IsInitialBlockDownload() &&
|
||||
vWeightedPeers.size() >= 2 &&
|
||||
vWeightedPeers.size() < HEADER_REDUNDANT_PEER_THRESHOLD &&
|
||||
!mi->second.fRequested)
|
||||
{
|
||||
CNode* pnode2 = vWeightedPeers[(nPeerIndex + 1) % vWeightedPeers.size()];
|
||||
if (pnode2 != pnode)
|
||||
pnode2->AskFor(CInv(MSG_BLOCK, hash));
|
||||
}
|
||||
|
||||
if (!mi->second.fRequested || nNow - mi->second.nLastRequestTime >= HEADER_REQUEST_TIMEOUT_MICROS)
|
||||
{
|
||||
if (!mi->second.fRequested)
|
||||
mi->second.nFirstRequestTime = nNow;
|
||||
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
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
@@ -1843,10 +1324,12 @@ bool IsInitialBlockDownload()
|
||||
pindexLastBest = pindexBest;
|
||||
nLastUpdate = GetTime();
|
||||
}
|
||||
// IBD is complete once we've passed the checkpoint height estimate.
|
||||
// The previous >24h block-time check incorrectly kept IBD true when the
|
||||
// chain was synced but simply stalled (no new blocks arriving), which
|
||||
// prevented the stake miner from ever proceeding.
|
||||
// IBD is complete once we've passed the checkpoint AND the chain tip is
|
||||
// recent (within 24h). This prevents a stall AFTER checkpoint from
|
||||
// permanently disabling header fetching. The forcestaking path above
|
||||
// handles the specific staking-broker scenario.
|
||||
if (GetTime() - nLastUpdate > 24 * 60 * 60)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -3672,7 +3155,7 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock)
|
||||
if (!pblock->AcceptBlock())
|
||||
return error("ProcessBlock() : AcceptBlock FAILED");
|
||||
|
||||
MarkHeaderSyncBlockAccepted(hash);
|
||||
g_syncManager.BlockAccepted(hash);
|
||||
|
||||
// Recursively process any orphan blocks that depended on this one
|
||||
vector<uint256> vWorkQueue;
|
||||
@@ -3688,7 +3171,7 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock)
|
||||
if (pblockOrphan->AcceptBlock())
|
||||
{
|
||||
vWorkQueue.push_back(pblockOrphan->GetHash());
|
||||
MarkHeaderSyncBlockAccepted(pblockOrphan->GetHash());
|
||||
g_syncManager.BlockAccepted(pblockOrphan->GetHash());
|
||||
}
|
||||
mapOrphanBlocks.erase(pblockOrphan->GetHash());
|
||||
setStakeSeenOrphan.erase(pblockOrphan->GetProofOfStake());
|
||||
@@ -3702,16 +3185,16 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock)
|
||||
if (IsInitialBlockDownload())
|
||||
{
|
||||
const unsigned int nQueued =
|
||||
(hashBestHeaderSync != 0) ? QueueHeaderSyncBlocksParallel(HEADER_DOWNLOAD_WINDOW) : 0;
|
||||
(g_syncManager.GetBestHeader() != 0) ? g_syncManager.QueueBlocksParallel() : 0;
|
||||
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());
|
||||
|
||||
const unsigned int nPlannerDepth = GetHeaderSyncPlannerDepth();
|
||||
if (nPlannerDepth <= HEADER_SYNC_LOW_WATER)
|
||||
const unsigned int nPlannerDepth = g_syncManager.GetPlannerDepth();
|
||||
if (nPlannerDepth <= CSyncManager::HEADER_SYNC_LOW_WATER)
|
||||
{
|
||||
const unsigned int nRefilled = RequestHeaderSyncRefillAllPeers(
|
||||
hashBestHeaderSync, HEADER_SYNC_REFILL_MIN_INTERVAL_SECONDS,
|
||||
const unsigned int nRefilled = g_syncManager.RequestRefillAllPeers(
|
||||
g_syncManager.GetBestHeader(), CSyncManager::HEADER_SYNC_REFILL_MIN_INTERVAL_SECONDS,
|
||||
(nPlannerDepth == 0) ? "post-accept planner empty" : "post-accept planner low-water");
|
||||
if (nRefilled > 0)
|
||||
printf("IBD-DIAG: post-accept requested headers from %u peers at plannerDepth=%u after block %s\n",
|
||||
@@ -4562,14 +4045,23 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
|
||||
// parallelism. Multiple peers sending overlapping inv ranges is harmless
|
||||
// (AlreadyHave filters duplicates) but ensures we discover and download
|
||||
// blocks from the fastest available source.
|
||||
// NOTE: nStartingHeight from version messages is unverified. Peers can
|
||||
// claim any height. During IBD we always ask all eligible peers rather
|
||||
// than filtering on a claim that may be wrong (a stunted node could be
|
||||
// reporting the full chain height while only serving the tail of its
|
||||
// own fork). Use nBestKnownHeight (updated from actual block responses)
|
||||
// for peer capability assessment instead.
|
||||
static int nAskedForBlocks = 0;
|
||||
bool fIBD = IsInitialBlockDownload();
|
||||
bool fBehindPeer = (pfrom->nStartingHeight > nBestHeight);
|
||||
// During IBD: ask every non-client peer unconditionally to maximise
|
||||
// download sources. Post-IBD: use traditional height-check logic.
|
||||
bool fShouldAsk = !pfrom->fClient && !pfrom->fOneShot &&
|
||||
(pfrom->nStartingHeight > (nBestHeight - 144)) &&
|
||||
(fIBD ||
|
||||
pfrom->nStartingHeight > (nBestHeight - 144) ||
|
||||
pfrom->nStartingHeight > nBestHeight) &&
|
||||
(pfrom->nVersion < NOBLKS_VERSION_START ||
|
||||
pfrom->nVersion >= NOBLKS_VERSION_END) &&
|
||||
(fIBD || nAskedForBlocks < 1 || vNodes.size() <= 1 || fBehindPeer);
|
||||
(fIBD || nAskedForBlocks < 1 || vNodes.size() <= 1 || pfrom->nStartingHeight > nBestHeight);
|
||||
printf("IBD-DIAG: version handler: peer=%s height=%d ourHeight=%d fClient=%d fOneShot=%d shouldAsk=%d nAskedForBlocks=%d IBD=%d\n",
|
||||
pfrom->addr.ToString().c_str(), pfrom->nStartingHeight, nBestHeight,
|
||||
pfrom->fClient, pfrom->fOneShot, fShouldAsk, nAskedForBlocks, fIBD);
|
||||
@@ -4582,7 +4074,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
|
||||
// so we learn about future blocks much faster. The headers handler
|
||||
// will AskFor each unknown block, pre-populating the download queue.
|
||||
if (fIBD)
|
||||
RequestHeaderSyncRefill(pfrom, hashBestHeaderSync, 0, "version bootstrap");
|
||||
g_syncManager.RequestRefill(pfrom, g_syncManager.GetBestHeader(), 0, "version bootstrap");
|
||||
printf("IBD-DIAG: sent getblocks%s from height %d to peer %s\n",
|
||||
fIBD ? "+getheaders" : "", nBestHeight, pfrom->addr.ToString().c_str());
|
||||
}
|
||||
@@ -4972,97 +4464,8 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
|
||||
{
|
||||
vector<CBlock> vHeaders;
|
||||
vRecv >> vHeaders;
|
||||
if (vHeaders.size() > 2000)
|
||||
{
|
||||
pfrom->Misbehaving(20);
|
||||
return error("message headers size() = %" PRIszu "", vHeaders.size());
|
||||
}
|
||||
|
||||
uint256 hashChainTip = 0;
|
||||
int nNewHeaders = 0;
|
||||
for (const CBlock& header : vHeaders)
|
||||
{
|
||||
if (!header.vtx.empty())
|
||||
{
|
||||
pfrom->Misbehaving(20);
|
||||
return error("headers message includes transactions");
|
||||
}
|
||||
|
||||
const uint256 hashHeader = header.GetHash();
|
||||
if (mapBlockIndex.count(hashHeader) || mapHeaderSync.count(hashHeader))
|
||||
{
|
||||
hashChainTip = hashHeader;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hashChainTip != 0)
|
||||
{
|
||||
if (header.hashPrevBlock != hashChainTip)
|
||||
{
|
||||
pfrom->Misbehaving(20);
|
||||
return error("non-continuous headers sequence");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto miPrev = mapBlockIndex.find(header.hashPrevBlock);
|
||||
if (miPrev == mapBlockIndex.end() && !mapHeaderSync.count(header.hashPrevBlock))
|
||||
break;
|
||||
}
|
||||
|
||||
if (!AddHeaderSyncNode(header, hashHeader))
|
||||
{
|
||||
pfrom->Misbehaving(20);
|
||||
return error("invalid header sequence");
|
||||
}
|
||||
|
||||
hashChainTip = hashHeader;
|
||||
nNewHeaders++;
|
||||
}
|
||||
|
||||
int nRequested = 0;
|
||||
if (hashBestHeaderSync != 0)
|
||||
nRequested = QueueHeaderSyncBlocksParallel(HEADER_DOWNLOAD_WINDOW);
|
||||
|
||||
if (nNewHeaders > 0)
|
||||
nLastNewHeaderTime = GetTime();
|
||||
|
||||
if (nNewHeaders > 0 || nRequested > 0)
|
||||
printf("IBD-DIAG: accepted %d new headers, queued %d blocks from %zu headers (peer=%s bestHeader=%s)\n",
|
||||
nNewHeaders, nRequested, vHeaders.size(), pfrom->addr.ToString().c_str(),
|
||||
hashBestHeaderSync.ToString().substr(0,20).c_str());
|
||||
|
||||
// If we received a full batch, continue fetching headers.
|
||||
// During IBD, prefer getheaders over getblocks since headers are ~80 bytes
|
||||
// vs full blocks, letting us discover the chain structure faster.
|
||||
if (vHeaders.size() >= 2000)
|
||||
{
|
||||
if (IsInitialBlockDownload() && hashChainTip != 0)
|
||||
ContinueHeaderSync(pfrom, hashChainTip);
|
||||
else
|
||||
pfrom->PushGetBlocks(pindexBest, uint256(0));
|
||||
}
|
||||
else if (IsInitialBlockDownload() && nNewHeaders > 0 && hashChainTip != 0)
|
||||
{
|
||||
// Partial batch with new content. The peer either truncated its
|
||||
// response (e.g. send-buffer pressure on Tor) or is briefly at the
|
||||
// tip of what it knows. Either way the v5.9.2 fix only refilled
|
||||
// when the cache fully drained, so a partial batch could leave the
|
||||
// pipeline silently parked. Ask this peer to continue from the
|
||||
// highest header we now know — covers truncated responses, and
|
||||
// costs at most one empty headers reply when the peer is honestly
|
||||
// at the chain tip.
|
||||
ContinueHeaderSync(pfrom, hashChainTip);
|
||||
}
|
||||
else if (IsInitialBlockDownload())
|
||||
{
|
||||
const unsigned int nPlannerDepth = GetHeaderSyncPlannerDepth();
|
||||
if (nPlannerDepth <= HEADER_SYNC_LOW_WATER)
|
||||
RequestHeaderSyncRefill(
|
||||
pfrom, (hashChainTip != 0) ? hashChainTip : hashBestHeaderSync,
|
||||
HEADER_SYNC_REFILL_MIN_INTERVAL_SECONDS,
|
||||
(nPlannerDepth == 0) ? "headers planner empty" : "headers planner low-water");
|
||||
}
|
||||
if (!g_syncManager.ProcessHeaders(pfrom, vHeaders))
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -5154,24 +4557,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
|
||||
CInv inv(MSG_BLOCK, hashBlock);
|
||||
pfrom->AddInventoryKnown(inv);
|
||||
|
||||
// Track block delivery and measure latency for adaptive timeouts
|
||||
pfrom->nBlocksDelivered++;
|
||||
if (nBestHeight > pfrom->nBestKnownHeight)
|
||||
pfrom->nBestKnownHeight = nBestHeight;
|
||||
|
||||
// Update rolling average latency (exponential moving average, 7/8 old + 1/8 new)
|
||||
{
|
||||
int64_t nRequestTime = GetHeaderSyncRequestTime(hashBlock);
|
||||
if (nRequestTime > 0) {
|
||||
int64_t nLatency = GetTime() * 1000000 - nRequestTime;
|
||||
if (nLatency > 0) {
|
||||
if (pfrom->nAvgBlockLatencyUs == 0)
|
||||
pfrom->nAvgBlockLatencyUs = nLatency;
|
||||
else
|
||||
pfrom->nAvgBlockLatencyUs = (pfrom->nAvgBlockLatencyUs * 7 + nLatency) / 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
g_syncManager.TrackBlockDelivery(pfrom, hashBlock);
|
||||
|
||||
if (ProcessBlock(pfrom, &block))
|
||||
{
|
||||
@@ -5180,7 +4566,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
|
||||
if (IsInitialBlockDownload())
|
||||
{
|
||||
// Keep download window full after every accepted block
|
||||
QueueHeaderSyncBlocksParallel(HEADER_DOWNLOAD_WINDOW);
|
||||
g_syncManager.QueueBlocksParallel();
|
||||
|
||||
static int nBlocksSinceRequest = 0;
|
||||
if (++nBlocksSinceRequest >= 500)
|
||||
@@ -5982,18 +5368,18 @@ bool SendMessages(CNode* pto, bool fSendTrickle)
|
||||
{
|
||||
pto->pindexLastGetHeadersBegin = nullptr;
|
||||
|
||||
uint256 hashLocatorTip = hashBestHeaderSync;
|
||||
uint256 hashLocatorTip = g_syncManager.GetBestHeader();
|
||||
if (hashLocatorTip == 0 && nHighestInvWalk > nBestHeight &&
|
||||
hashHighestInvWalk != 0 && mapBlockIndex.count(hashHighestInvWalk))
|
||||
{
|
||||
hashLocatorTip = hashHighestInvWalk;
|
||||
}
|
||||
|
||||
unsigned int nRefilled = RequestHeaderSyncRefillAllPeers(
|
||||
unsigned int nRefilled = g_syncManager.RequestRefillAllPeers(
|
||||
hashLocatorTip,
|
||||
0,
|
||||
"stall-recovery");
|
||||
unsigned int nQueued = QueueHeaderSyncBlocksParallel(HEADER_DOWNLOAD_WINDOW);
|
||||
unsigned int nQueued = g_syncManager.QueueBlocksParallel();
|
||||
|
||||
printf("SYNC-DIAG: stall recovery used headers-first path (locator=%s, refillPeers=%u, queued=%u)\n",
|
||||
hashLocatorTip.ToString().substr(0,20).c_str(),
|
||||
@@ -6024,84 +5410,9 @@ bool SendMessages(CNode* pto, bool fSendTrickle)
|
||||
}
|
||||
}
|
||||
|
||||
// Per-peer IBD getheaders heartbeat. The v5.9.2 belt-and-suspenders
|
||||
// had three holes that this replaces:
|
||||
// 1. It only fired when hashBestHeaderSync == 0 (cache fully empty);
|
||||
// a few stale in-flight entries blocked refill until 15-min TTL.
|
||||
// 2. The throttle was process-wide, so an unresponsive peer could
|
||||
// "absorb" the one-per-30s request and leave others unkicked.
|
||||
// 3. It had no path for "peer stopped feeding mid-batch" — only
|
||||
// total-cache-drain triggered it.
|
||||
//
|
||||
// Per-peer heartbeat with an adaptive interval covers all three:
|
||||
// - Low-water mode (cache below the download window): 15s, refills
|
||||
// before the planner runs dry without waiting for cache exhaustion.
|
||||
// - Steady mode (cache filled): 60s, keeps each peer's view of our
|
||||
// locator fresh so a peer that goes silent gets re-asked, and a
|
||||
// peer that catches up between calls can announce new headers.
|
||||
// An empty headers response is ~14 bytes — cheap on Tor, no abuse risk.
|
||||
if (!pto->fClient && pto->nVersion != 0 && IsInitialBlockDownload())
|
||||
{
|
||||
const int64_t nNowSec = GetTime();
|
||||
const unsigned int nPlannerDepth = GetHeaderSyncPlannerDepth();
|
||||
const unsigned int nInFlight = CountHeaderSyncInFlight();
|
||||
static int64_t nLastHeaderPlannerControl = 0;
|
||||
static int64_t nLastHeaderWatchdog = 0;
|
||||
static int64_t nLastBlockPlannerControl = 0;
|
||||
|
||||
if (nLastNewHeaderTime == 0)
|
||||
nLastNewHeaderTime = nNowSec;
|
||||
|
||||
if (nNowSec - nLastHeaderPlannerControl >= HEADER_SYNC_CONTROL_INTERVAL_SECONDS &&
|
||||
nPlannerDepth < HEADER_SYNC_LOW_WATER &&
|
||||
nInFlight < HEADER_SYNC_TARGET_INFLIGHT)
|
||||
{
|
||||
const unsigned int nRefilled = RequestHeaderSyncRefillAllPeers(
|
||||
hashBestHeaderSync, HEADER_SYNC_REFILL_MIN_INTERVAL_SECONDS,
|
||||
"control-loop");
|
||||
if (nRefilled > 0)
|
||||
printf("IBD-DIAG: control-loop refill from %u peers (plannerDepth=%u inflight=%u target=%u)\n",
|
||||
nRefilled, nPlannerDepth, nInFlight, HEADER_SYNC_TARGET_INFLIGHT);
|
||||
nLastHeaderPlannerControl = nNowSec;
|
||||
}
|
||||
|
||||
if (nNowSec - nLastHeaderWatchdog >= HEADER_SYNC_CONTROL_INTERVAL_SECONDS &&
|
||||
nNowSec - nLastNewHeaderTime >= HEADER_SYNC_WATCHDOG_SECONDS)
|
||||
{
|
||||
const unsigned int nRefilled = RequestHeaderSyncRefillAllPeers(
|
||||
hashBestHeaderSync, HEADER_SYNC_REFILL_MIN_INTERVAL_SECONDS,
|
||||
"headers-watchdog");
|
||||
if (nRefilled > 0)
|
||||
printf("IBD-DIAG: headers watchdog refill from %u peers after %llds without new headers (plannerDepth=%u inflight=%u)\n",
|
||||
nRefilled,
|
||||
(long long)(nNowSec - nLastNewHeaderTime),
|
||||
nPlannerDepth,
|
||||
nInFlight);
|
||||
nLastHeaderWatchdog = nNowSec;
|
||||
}
|
||||
|
||||
const int64_t nMinInterval =
|
||||
(mapHeaderSync.size() < HEADER_DOWNLOAD_WINDOW) ? 15 : 60;
|
||||
|
||||
if (nNowSec - pto->nLastIbdHeaderRequest >= nMinInterval)
|
||||
RequestHeaderSyncRefill(pto, hashBestHeaderSync, nMinInterval, "heartbeat");
|
||||
|
||||
// Keep the block planner alive even when no new headers arrive and
|
||||
// no blocks are being accepted. Without this periodic kick, the
|
||||
// redundant-request and timeout logic inside QueueHeaderSyncBlocksParallel()
|
||||
// only runs on header arrivals or block acceptance, so IBD can park
|
||||
// indefinitely behind one missing frontier block.
|
||||
if (nNowSec - nLastBlockPlannerControl >= HEADER_SYNC_CONTROL_INTERVAL_SECONDS &&
|
||||
hashBestHeaderSync != 0 &&
|
||||
nPlannerDepth > 0)
|
||||
{
|
||||
const unsigned int nRequeued = QueueHeaderSyncBlocksParallel(HEADER_DOWNLOAD_WINDOW);
|
||||
if (nRequeued > 0)
|
||||
printf("IBD-DIAG: block-planner control queued %u block requests (plannerDepth=%u inflight=%u)\n",
|
||||
nRequeued, nPlannerDepth, nInFlight);
|
||||
nLastBlockPlannerControl = nNowSec;
|
||||
}
|
||||
}
|
||||
// Per-peer IBD getheaders heartbeat and block-planner cadence.
|
||||
// Logic lives in CSyncManager::Tick — see syncmanager.cpp.
|
||||
g_syncManager.Tick(pto, nHighestInvWalk, hashHighestInvWalk);
|
||||
|
||||
//
|
||||
// Message: getdata
|
||||
@@ -6112,9 +5423,9 @@ bool SendMessages(CNode* pto, bool fSendTrickle)
|
||||
if (GetTime() - nLastStatus >= 15) {
|
||||
printf("IBD-DIAG: STATUS height=%d plannerHeight=%d plannerDepth=%u inflight=%u peers=%d askfor_queued=%d orphans=%d\n",
|
||||
nBestHeight,
|
||||
GetHeaderSyncPlannerHeight(),
|
||||
GetHeaderSyncPlannerDepth(),
|
||||
CountHeaderSyncInFlight(),
|
||||
g_syncManager.GetPlannerHeight(),
|
||||
g_syncManager.GetPlannerDepth(),
|
||||
g_syncManager.CountInFlight(),
|
||||
(int)vNodes.size(),
|
||||
(int)pto->mapAskFor.size(),
|
||||
(int)mapOrphanBlocks.size());
|
||||
|
||||
@@ -545,7 +545,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
|
||||
int64_t nFee = nTransactionFee * (1 + (int64_t)nBytes / 1000);
|
||||
|
||||
// Min Fee
|
||||
int64_t nMinFee = txDummy.GetMinFee(1, GMF_SEND, nBytes);
|
||||
int64_t nMinFee = txDummy.GetMinFee(1, GetMinFeeMode::Send, nBytes);
|
||||
|
||||
nPayFee = max(nFee, nMinFee);
|
||||
|
||||
|
||||
@@ -0,0 +1,664 @@
|
||||
// Copyright (c) 2026 The Triangles developers
|
||||
// Distributed under the MIT/X11 software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include "syncmanager.h"
|
||||
|
||||
#include "bignum.h"
|
||||
#include "checkpoints.h"
|
||||
#include "main.h"
|
||||
#include "net.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
|
||||
struct CSyncManager::HeaderNode
|
||||
{
|
||||
CBlock header;
|
||||
int nHeight;
|
||||
uint256 nChainTrust;
|
||||
bool fRequested;
|
||||
int64_t nLastRequestTime;
|
||||
int64_t nFirstRequestTime;
|
||||
int64_t nInsertTime;
|
||||
};
|
||||
|
||||
namespace
|
||||
{
|
||||
static const unsigned int MAX_HEADER_SYNC_CACHE = 15000;
|
||||
static const size_t HEADER_REDUNDANT_PEER_THRESHOLD = 4;
|
||||
static const int64_t HEADER_REQUEST_TIMEOUT_MICROS = 60 * 1000000;
|
||||
static const int64_t HEADER_REDUNDANT_REQUEST_MICROS = 5 * 1000000;
|
||||
static const int64_t HEADER_SYNC_TTL_MICROS = 15 * 60 * 1000000;
|
||||
|
||||
std::map<uint256, CSyncManager::HeaderNode> mapHeaders;
|
||||
uint256 hashBestHeader = 0;
|
||||
int64_t nLastNewHeaderTime = 0;
|
||||
}
|
||||
|
||||
CSyncManager g_syncManager;
|
||||
|
||||
bool CSyncManager::HaveHeader(const uint256& hash) const
|
||||
{
|
||||
return mapHeaders.count(hash) != 0;
|
||||
}
|
||||
|
||||
uint256 CSyncManager::GetBestHeader() const
|
||||
{
|
||||
return hashBestHeader;
|
||||
}
|
||||
|
||||
std::size_t CSyncManager::GetHeaderCount() const
|
||||
{
|
||||
return mapHeaders.size();
|
||||
}
|
||||
|
||||
uint256 CSyncManager::GetHeaderTrust(unsigned int nBits) const
|
||||
{
|
||||
CBigNum bnTarget;
|
||||
bnTarget.SetCompact(nBits);
|
||||
|
||||
if (bnTarget <= 0)
|
||||
return 0;
|
||||
|
||||
return ((CBigNum(1) << 256) / (bnTarget + 1)).getuint256();
|
||||
}
|
||||
|
||||
bool CSyncManager::GetKnownHeaderState(const uint256& hash, int& nHeight, uint256& nChainTrust) const
|
||||
{
|
||||
std::map<uint256, CBlockIndex*>::const_iterator miBlock = mapBlockIndex.find(hash);
|
||||
if (miBlock != mapBlockIndex.end())
|
||||
{
|
||||
nHeight = miBlock->second->nHeight;
|
||||
nChainTrust = miBlock->second->nChainTrust;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::map<uint256, HeaderNode>::const_iterator miHeader = mapHeaders.find(hash);
|
||||
if (miHeader != mapHeaders.end())
|
||||
{
|
||||
nHeight = miHeader->second.nHeight;
|
||||
nChainTrust = miHeader->second.nChainTrust;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CSyncManager::GetPrevHash(const uint256& hash, uint256& hashPrev) const
|
||||
{
|
||||
std::map<uint256, HeaderNode>::const_iterator miHeader = mapHeaders.find(hash);
|
||||
if (miHeader != mapHeaders.end())
|
||||
{
|
||||
hashPrev = miHeader->second.header.hashPrevBlock;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::map<uint256, CBlockIndex*>::const_iterator miBlock = mapBlockIndex.find(hash);
|
||||
if (miBlock != mapBlockIndex.end() && miBlock->second->pprev)
|
||||
{
|
||||
hashPrev = miBlock->second->pprev->GetBlockHash();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void CSyncManager::RecomputeBestHeader()
|
||||
{
|
||||
hashBestHeader = 0;
|
||||
uint256 nBestTrust = 0;
|
||||
|
||||
for (std::map<uint256, HeaderNode>::const_iterator it = mapHeaders.begin(); it != mapHeaders.end(); ++it)
|
||||
{
|
||||
if (hashBestHeader == 0 || it->second.nChainTrust > nBestTrust)
|
||||
{
|
||||
hashBestHeader = it->first;
|
||||
nBestTrust = it->second.nChainTrust;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CSyncManager::PruneHeaders()
|
||||
{
|
||||
const int64_t nNow = GetTime() * 1000000;
|
||||
|
||||
if (mapHeaders.size() > MAX_HEADER_SYNC_CACHE / 2)
|
||||
{
|
||||
unsigned int nEvicted = 0;
|
||||
for (std::map<uint256, HeaderNode>::iterator it = mapHeaders.begin(); it != mapHeaders.end(); )
|
||||
{
|
||||
if (nNow - it->second.nInsertTime >= HEADER_SYNC_TTL_MICROS)
|
||||
{
|
||||
it = mapHeaders.erase(it);
|
||||
++nEvicted;
|
||||
}
|
||||
else
|
||||
++it;
|
||||
}
|
||||
if (nEvicted > 0)
|
||||
{
|
||||
printf("IBD-DIAG: TTL-evicted %u stale sync headers, %u remain\n",
|
||||
nEvicted, (unsigned int)mapHeaders.size());
|
||||
RecomputeBestHeader();
|
||||
}
|
||||
}
|
||||
|
||||
if (mapHeaders.size() > MAX_HEADER_SYNC_CACHE)
|
||||
{
|
||||
printf("IBD-DIAG: sync header cache exceeded %u entries, evicting oldest\n", MAX_HEADER_SYNC_CACHE);
|
||||
while (mapHeaders.size() > MAX_HEADER_SYNC_CACHE * 3 / 4)
|
||||
{
|
||||
std::map<uint256, HeaderNode>::iterator oldest = mapHeaders.begin();
|
||||
for (std::map<uint256, HeaderNode>::iterator it = mapHeaders.begin(); it != mapHeaders.end(); ++it)
|
||||
{
|
||||
if (it->second.nInsertTime < oldest->second.nInsertTime)
|
||||
oldest = it;
|
||||
}
|
||||
mapHeaders.erase(oldest);
|
||||
}
|
||||
RecomputeBestHeader();
|
||||
}
|
||||
}
|
||||
|
||||
bool CSyncManager::AddHeaderNode(const CBlock& header, const uint256& hashHeader)
|
||||
{
|
||||
if (mapBlockIndex.count(hashHeader) || mapHeaders.count(hashHeader))
|
||||
return true;
|
||||
|
||||
if (!header.vtx.empty())
|
||||
{
|
||||
printf("IBD-DIAG: header rejected (has vtx) hash=%s\n", hashHeader.ToString().substr(0,20).c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (header.GetBlockTime() > GetTime() + 15 * 60)
|
||||
{
|
||||
printf("IBD-DIAG: header rejected (future time) hash=%s time=%u\n",
|
||||
hashHeader.ToString().substr(0,20).c_str(), header.nTime);
|
||||
return false;
|
||||
}
|
||||
|
||||
int nPrevHeight = -1;
|
||||
uint256 nPrevChainTrust = 0;
|
||||
if (!GetKnownHeaderState(header.hashPrevBlock, nPrevHeight, nPrevChainTrust))
|
||||
{
|
||||
printf("IBD-DIAG: header rejected (prev unknown) hash=%s prevHash=%s\n",
|
||||
hashHeader.ToString().substr(0,20).c_str(),
|
||||
header.hashPrevBlock.ToString().substr(0,20).c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
const int nHeight = nPrevHeight + 1;
|
||||
if (nHeight <= CUTOFF_POW_BLOCK && !CheckProofOfWork(hashHeader, header.nBits))
|
||||
{
|
||||
printf("IBD-DIAG: header PoW FAILED at height %d hash=%s nBits=%08x prevHash=%s\n",
|
||||
nHeight, hashHeader.ToString().substr(0,20).c_str(), header.nBits,
|
||||
header.hashPrevBlock.ToString().substr(0,20).c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
HeaderNode node;
|
||||
node.header = header;
|
||||
node.nHeight = nHeight;
|
||||
node.nChainTrust = nPrevChainTrust + GetHeaderTrust(header.nBits);
|
||||
node.fRequested = false;
|
||||
node.nLastRequestTime = 0;
|
||||
node.nFirstRequestTime = 0;
|
||||
node.nInsertTime = GetTime() * 1000000;
|
||||
|
||||
mapHeaders.insert({hashHeader, node});
|
||||
|
||||
if (hashBestHeader == 0 || node.nChainTrust > mapHeaders[hashBestHeader].nChainTrust)
|
||||
hashBestHeader = hashHeader;
|
||||
|
||||
PruneHeaders();
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<uint256> CSyncManager::GetDownloadPath(uint256 hashTip) const
|
||||
{
|
||||
std::vector<uint256> vPath;
|
||||
|
||||
while (hashTip != 0 && !mapBlockIndex.count(hashTip))
|
||||
{
|
||||
std::map<uint256, HeaderNode>::const_iterator mi = mapHeaders.find(hashTip);
|
||||
if (mi == mapHeaders.end())
|
||||
break;
|
||||
|
||||
vPath.push_back(hashTip);
|
||||
hashTip = mi->second.header.hashPrevBlock;
|
||||
}
|
||||
|
||||
std::reverse(vPath.begin(), vPath.end());
|
||||
return vPath;
|
||||
}
|
||||
|
||||
unsigned int CSyncManager::CountInFlight() const
|
||||
{
|
||||
const int64_t nNow = GetTime() * 1000000;
|
||||
unsigned int nInFlight = 0;
|
||||
for (std::map<uint256, HeaderNode>::const_iterator it = mapHeaders.begin(); it != mapHeaders.end(); ++it)
|
||||
{
|
||||
if (it->second.fRequested && nNow - it->second.nLastRequestTime < HEADER_REQUEST_TIMEOUT_MICROS)
|
||||
++nInFlight;
|
||||
}
|
||||
return nInFlight;
|
||||
}
|
||||
|
||||
unsigned int CSyncManager::GetPlannerDepth() const
|
||||
{
|
||||
if (hashBestHeader == 0)
|
||||
return 0;
|
||||
|
||||
return (unsigned int)GetDownloadPath(hashBestHeader).size();
|
||||
}
|
||||
|
||||
int CSyncManager::GetPlannerHeight() const
|
||||
{
|
||||
if (hashBestHeader == 0)
|
||||
return pindexBest ? pindexBest->nHeight : -1;
|
||||
|
||||
std::map<uint256, HeaderNode>::const_iterator mi = mapHeaders.find(hashBestHeader);
|
||||
if (mi == mapHeaders.end())
|
||||
return pindexBest ? pindexBest->nHeight : -1;
|
||||
|
||||
return mi->second.nHeight;
|
||||
}
|
||||
|
||||
int64_t CSyncManager::GetRequestTime(const uint256& hashBlock) const
|
||||
{
|
||||
std::map<uint256, HeaderNode>::const_iterator mi = mapHeaders.find(hashBlock);
|
||||
if (mi == mapHeaders.end())
|
||||
return 0;
|
||||
return mi->second.nFirstRequestTime;
|
||||
}
|
||||
|
||||
void CSyncManager::BlockAccepted(const uint256& hashBlock)
|
||||
{
|
||||
std::map<uint256, HeaderNode>::iterator mi = mapHeaders.find(hashBlock);
|
||||
if (mi == mapHeaders.end())
|
||||
return;
|
||||
|
||||
mapHeaders.erase(mi);
|
||||
if (hashBestHeader == hashBlock)
|
||||
RecomputeBestHeader();
|
||||
}
|
||||
|
||||
void CSyncManager::ContinueHeaders(CNode* pfrom, const uint256& hashTip)
|
||||
{
|
||||
if (!pfrom || hashTip == 0)
|
||||
return;
|
||||
|
||||
std::vector<uint256> vHave;
|
||||
uint256 hashWalk = hashTip;
|
||||
int nStep = 1;
|
||||
|
||||
while (hashWalk != 0)
|
||||
{
|
||||
vHave.push_back(hashWalk);
|
||||
|
||||
for (int i = 0; i < nStep && hashWalk != 0; ++i)
|
||||
{
|
||||
uint256 hashPrev = 0;
|
||||
if (!GetPrevHash(hashWalk, hashPrev))
|
||||
hashWalk = 0;
|
||||
else
|
||||
hashWalk = hashPrev;
|
||||
}
|
||||
|
||||
if (vHave.size() > 10)
|
||||
nStep *= 2;
|
||||
}
|
||||
|
||||
vHave.push_back(!fTestNet ? hashGenesisBlockOfficial : hashGenesisBlockTestNet);
|
||||
pfrom->PushMessage("getheaders", CBlockLocator(vHave), uint256(0));
|
||||
}
|
||||
|
||||
bool CSyncManager::RequestRefill(CNode* pfrom, uint256 hashTip, int64_t nMinIntervalSeconds, const char* pszReason)
|
||||
{
|
||||
if (!pfrom || pfrom->fClient || pfrom->nVersion == 0 || !IsInitialBlockDownload())
|
||||
return false;
|
||||
|
||||
const int64_t nNowSec = GetTime();
|
||||
if (nMinIntervalSeconds > 0 &&
|
||||
nNowSec - pfrom->nLastIbdHeaderRequest < nMinIntervalSeconds)
|
||||
return false;
|
||||
|
||||
uint256 hashLocatorTip = hashTip;
|
||||
if (hashLocatorTip == 0 ||
|
||||
(!mapBlockIndex.count(hashLocatorTip) && !mapHeaders.count(hashLocatorTip)))
|
||||
{
|
||||
hashLocatorTip = hashBestHeader;
|
||||
}
|
||||
|
||||
if (hashLocatorTip != 0 && (!pindexBest || hashLocatorTip != pindexBest->GetBlockHash()))
|
||||
{
|
||||
ContinueHeaders(pfrom, hashLocatorTip);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!pindexBest)
|
||||
return false;
|
||||
|
||||
pfrom->pindexLastGetHeadersBegin = NULL;
|
||||
pfrom->PushGetHeaders(pindexBest, uint256(0));
|
||||
hashLocatorTip = pindexBest->GetBlockHash();
|
||||
}
|
||||
|
||||
pfrom->nLastIbdHeaderRequest = nNowSec;
|
||||
printf("IBD-DIAG: %s getheaders to peer=%s locator=%s plannerDepth=%u inflight=%u\n",
|
||||
pszReason, pfrom->addr.ToString().c_str(),
|
||||
hashLocatorTip.ToString().substr(0,20).c_str(),
|
||||
GetPlannerDepth(), CountInFlight());
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned int CSyncManager::RequestRefillAllPeers(uint256 hashTip, int64_t nMinIntervalSeconds, const char* pszReason)
|
||||
{
|
||||
std::vector<CNode*> vEligiblePeers;
|
||||
{
|
||||
LOCK(cs_vNodes);
|
||||
for (CNode* pnode : vNodes)
|
||||
{
|
||||
if (!pnode->fClient && pnode->nVersion != 0 && !pnode->fDisconnect)
|
||||
vEligiblePeers.push_back(pnode);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int nRequested = 0;
|
||||
for (CNode* pnode : vEligiblePeers)
|
||||
{
|
||||
if (RequestRefill(pnode, hashTip, nMinIntervalSeconds, pszReason))
|
||||
++nRequested;
|
||||
}
|
||||
|
||||
return nRequested;
|
||||
}
|
||||
|
||||
unsigned int CSyncManager::QueueBlocksParallel(unsigned int nWindow)
|
||||
{
|
||||
if (hashBestHeader == 0)
|
||||
return 0;
|
||||
|
||||
const std::vector<uint256> vPath = GetDownloadPath(hashBestHeader);
|
||||
if (vPath.empty())
|
||||
return 0;
|
||||
|
||||
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 = CountInFlight();
|
||||
unsigned int nQueued = 0;
|
||||
unsigned int nPeerIndex = 0;
|
||||
|
||||
std::sort(vEligiblePeers.begin(), vEligiblePeers.end(),
|
||||
[](const CNode* a, const CNode* b) {
|
||||
return a->nBlocksDelivered > b->nBlocksDelivered;
|
||||
});
|
||||
|
||||
std::vector<CNode*> vWeightedPeers;
|
||||
for (size_t i = 0; i < vEligiblePeers.size(); i++)
|
||||
{
|
||||
int nWeight = (i == 0) ? 3 : (i == 1) ? 2 : 1;
|
||||
for (int w = 0; w < nWeight; w++)
|
||||
vWeightedPeers.push_back(vEligiblePeers[i]);
|
||||
}
|
||||
|
||||
int64_t nAdaptiveTimeout = HEADER_REQUEST_TIMEOUT_MICROS;
|
||||
{
|
||||
int64_t nTotalLatency = 0;
|
||||
int nPeersWithLatency = 0;
|
||||
for (const CNode* pnode : vEligiblePeers)
|
||||
{
|
||||
if (pnode->nAvgBlockLatencyUs > 0)
|
||||
{
|
||||
nTotalLatency += pnode->nAvgBlockLatencyUs;
|
||||
++nPeersWithLatency;
|
||||
}
|
||||
}
|
||||
if (nPeersWithLatency > 0)
|
||||
{
|
||||
int64_t nAvgLatency = nTotalLatency / nPeersWithLatency;
|
||||
nAdaptiveTimeout = std::max((int64_t)(10 * 1000000),
|
||||
std::min((int64_t)(60 * 1000000), nAvgLatency * 5));
|
||||
}
|
||||
}
|
||||
|
||||
for (std::vector<uint256>::const_iterator it = vPath.begin(); it != vPath.end(); ++it)
|
||||
{
|
||||
if (nInFlight + nQueued >= nWindow)
|
||||
break;
|
||||
|
||||
std::map<uint256, HeaderNode>::iterator mi = mapHeaders.find(*it);
|
||||
if (mi == mapHeaders.end())
|
||||
continue;
|
||||
|
||||
bool fNeedsRequest = false;
|
||||
if (!mi->second.fRequested)
|
||||
fNeedsRequest = true;
|
||||
else if (nNow - mi->second.nLastRequestTime >= nAdaptiveTimeout)
|
||||
fNeedsRequest = true;
|
||||
else if (nNow - mi->second.nLastRequestTime >= HEADER_REDUNDANT_REQUEST_MICROS)
|
||||
fNeedsRequest = true;
|
||||
|
||||
if (!fNeedsRequest)
|
||||
continue;
|
||||
|
||||
CNode* pnode = vWeightedPeers[nPeerIndex % vWeightedPeers.size()];
|
||||
pnode->AskFor(CInv(MSG_BLOCK, *it));
|
||||
|
||||
if (IsInitialBlockDownload() &&
|
||||
vWeightedPeers.size() >= 2 &&
|
||||
vWeightedPeers.size() < HEADER_REDUNDANT_PEER_THRESHOLD &&
|
||||
!mi->second.fRequested)
|
||||
{
|
||||
CNode* pnode2 = vWeightedPeers[(nPeerIndex + 1) % vWeightedPeers.size()];
|
||||
if (pnode2 != pnode)
|
||||
pnode2->AskFor(CInv(MSG_BLOCK, *it));
|
||||
}
|
||||
|
||||
if (!mi->second.fRequested || nNow - mi->second.nLastRequestTime >= HEADER_REQUEST_TIMEOUT_MICROS)
|
||||
{
|
||||
if (!mi->second.fRequested)
|
||||
mi->second.nFirstRequestTime = nNow;
|
||||
mi->second.fRequested = true;
|
||||
mi->second.nLastRequestTime = nNow;
|
||||
}
|
||||
|
||||
++nQueued;
|
||||
++nPeerIndex;
|
||||
}
|
||||
|
||||
if (nQueued > 0)
|
||||
printf("IBD-DIAG: sync manager queued %u blocks across %zu peers (window=%u, inflight=%u)\n",
|
||||
nQueued, vEligiblePeers.size(), nWindow, nInFlight);
|
||||
|
||||
return nQueued;
|
||||
}
|
||||
|
||||
bool CSyncManager::ProcessHeaders(CNode* pfrom, const std::vector<CBlock>& vHeaders)
|
||||
{
|
||||
if (vHeaders.size() > 2000)
|
||||
{
|
||||
pfrom->Misbehaving(20);
|
||||
return error("message headers size() = %" PRIszu "", vHeaders.size());
|
||||
}
|
||||
|
||||
uint256 hashChainTip = 0;
|
||||
int nNewHeaders = 0;
|
||||
for (const CBlock& header : vHeaders)
|
||||
{
|
||||
if (!header.vtx.empty())
|
||||
{
|
||||
pfrom->Misbehaving(20);
|
||||
return error("headers message includes transactions");
|
||||
}
|
||||
|
||||
const uint256 hashHeader = header.GetHash();
|
||||
if (mapBlockIndex.count(hashHeader) || mapHeaders.count(hashHeader))
|
||||
{
|
||||
hashChainTip = hashHeader;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hashChainTip != 0)
|
||||
{
|
||||
if (header.hashPrevBlock != hashChainTip)
|
||||
{
|
||||
pfrom->Misbehaving(20);
|
||||
return error("non-continuous headers sequence");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::map<uint256, CBlockIndex*>::iterator miPrev = mapBlockIndex.find(header.hashPrevBlock);
|
||||
if (miPrev == mapBlockIndex.end() && !mapHeaders.count(header.hashPrevBlock))
|
||||
break;
|
||||
}
|
||||
|
||||
if (!AddHeaderNode(header, hashHeader))
|
||||
{
|
||||
pfrom->Misbehaving(20);
|
||||
return error("invalid header sequence");
|
||||
}
|
||||
|
||||
hashChainTip = hashHeader;
|
||||
nNewHeaders++;
|
||||
}
|
||||
|
||||
int nRequested = 0;
|
||||
if (hashBestHeader != 0)
|
||||
nRequested = QueueBlocksParallel(HEADER_DOWNLOAD_WINDOW);
|
||||
|
||||
if (nNewHeaders > 0)
|
||||
nLastNewHeaderTime = GetTime();
|
||||
|
||||
if (nNewHeaders > 0 || nRequested > 0)
|
||||
printf("IBD-DIAG: accepted %d new headers, queued %d blocks from %zu headers (peer=%s bestHeader=%s)\n",
|
||||
nNewHeaders, nRequested, vHeaders.size(), pfrom->addr.ToString().c_str(),
|
||||
hashBestHeader.ToString().substr(0,20).c_str());
|
||||
|
||||
if (vHeaders.size() >= 2000)
|
||||
{
|
||||
if (IsInitialBlockDownload() && hashChainTip != 0)
|
||||
ContinueHeaders(pfrom, hashChainTip);
|
||||
else
|
||||
pfrom->PushGetBlocks(pindexBest, uint256(0));
|
||||
}
|
||||
else if (IsInitialBlockDownload() && nNewHeaders > 0 && hashChainTip != 0)
|
||||
{
|
||||
ContinueHeaders(pfrom, hashChainTip);
|
||||
}
|
||||
else if (IsInitialBlockDownload())
|
||||
{
|
||||
const unsigned int nPlannerDepth = GetPlannerDepth();
|
||||
if (nPlannerDepth <= HEADER_SYNC_LOW_WATER)
|
||||
RequestRefill(
|
||||
pfrom, (hashChainTip != 0) ? hashChainTip : hashBestHeader,
|
||||
HEADER_SYNC_REFILL_MIN_INTERVAL_SECONDS,
|
||||
(nPlannerDepth == 0) ? "headers planner empty" : "headers planner low-water");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CSyncManager::TrackBlockDelivery(CNode* pfrom, const uint256& hashBlock)
|
||||
{
|
||||
if (!pfrom)
|
||||
return;
|
||||
|
||||
pfrom->nBlocksDelivered++;
|
||||
if (nBestHeight > pfrom->nBestKnownHeight)
|
||||
pfrom->nBestKnownHeight = nBestHeight;
|
||||
|
||||
int64_t nRequestTime = GetRequestTime(hashBlock);
|
||||
if (nRequestTime > 0)
|
||||
{
|
||||
int64_t nLatency = GetTime() * 1000000 - nRequestTime;
|
||||
if (nLatency > 0)
|
||||
{
|
||||
if (pfrom->nAvgBlockLatencyUs == 0)
|
||||
pfrom->nAvgBlockLatencyUs = nLatency;
|
||||
else
|
||||
pfrom->nAvgBlockLatencyUs = (pfrom->nAvgBlockLatencyUs * 7 + nLatency) / 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CSyncManager::Tick(CNode* pto, int nHighestInvWalk, const uint256& hashHighestInvWalk)
|
||||
{
|
||||
if (!pto || pto->fClient || pto->nVersion == 0 || !IsInitialBlockDownload())
|
||||
return;
|
||||
|
||||
const int64_t nNowSec = GetTime();
|
||||
const unsigned int nPlannerDepth = GetPlannerDepth();
|
||||
const unsigned int nInFlight = CountInFlight();
|
||||
static int64_t nLastHeaderPlannerControl = 0;
|
||||
static int64_t nLastHeaderWatchdog = 0;
|
||||
static int64_t nLastBlockPlannerControl = 0;
|
||||
|
||||
if (nLastNewHeaderTime == 0)
|
||||
nLastNewHeaderTime = nNowSec;
|
||||
|
||||
if (nNowSec - nLastHeaderPlannerControl >= HEADER_SYNC_CONTROL_INTERVAL_SECONDS &&
|
||||
nPlannerDepth < HEADER_SYNC_LOW_WATER &&
|
||||
nInFlight < HEADER_SYNC_TARGET_INFLIGHT)
|
||||
{
|
||||
const unsigned int nRefilled = RequestRefillAllPeers(
|
||||
hashBestHeader, HEADER_SYNC_REFILL_MIN_INTERVAL_SECONDS,
|
||||
"control-loop");
|
||||
if (nRefilled > 0)
|
||||
printf("IBD-DIAG: control-loop refill from %u peers (plannerDepth=%u inflight=%u target=%u)\n",
|
||||
nRefilled, nPlannerDepth, nInFlight, HEADER_SYNC_TARGET_INFLIGHT);
|
||||
nLastHeaderPlannerControl = nNowSec;
|
||||
}
|
||||
|
||||
if (nNowSec - nLastHeaderWatchdog >= HEADER_SYNC_CONTROL_INTERVAL_SECONDS &&
|
||||
nNowSec - nLastNewHeaderTime >= HEADER_SYNC_WATCHDOG_SECONDS)
|
||||
{
|
||||
const unsigned int nRefilled = RequestRefillAllPeers(
|
||||
hashBestHeader, HEADER_SYNC_REFILL_MIN_INTERVAL_SECONDS,
|
||||
"headers-watchdog");
|
||||
if (nRefilled > 0)
|
||||
printf("IBD-DIAG: headers watchdog refill from %u peers after %llds without new headers (plannerDepth=%u inflight=%u)\n",
|
||||
nRefilled,
|
||||
(long long)(nNowSec - nLastNewHeaderTime),
|
||||
nPlannerDepth,
|
||||
nInFlight);
|
||||
nLastHeaderWatchdog = nNowSec;
|
||||
}
|
||||
|
||||
const int64_t nMinInterval = (mapHeaders.size() < HEADER_DOWNLOAD_WINDOW) ? 15 : 60;
|
||||
if (nNowSec - pto->nLastIbdHeaderRequest >= nMinInterval)
|
||||
RequestRefill(pto, hashBestHeader, nMinInterval, "heartbeat");
|
||||
|
||||
if (nNowSec - nLastBlockPlannerControl >= HEADER_SYNC_CONTROL_INTERVAL_SECONDS &&
|
||||
hashBestHeader != 0 &&
|
||||
nPlannerDepth > 0)
|
||||
{
|
||||
const unsigned int nRequeued = QueueBlocksParallel(HEADER_DOWNLOAD_WINDOW);
|
||||
if (nRequeued > 0)
|
||||
printf("IBD-DIAG: block-planner control queued %u block requests (plannerDepth=%u inflight=%u)\n",
|
||||
nRequeued, nPlannerDepth, nInFlight);
|
||||
nLastBlockPlannerControl = nNowSec;
|
||||
}
|
||||
|
||||
if (hashBestHeader == 0 && nHighestInvWalk > nBestHeight &&
|
||||
hashHighestInvWalk != 0 && mapBlockIndex.count(hashHighestInvWalk))
|
||||
{
|
||||
RequestRefill(pto, hashHighestInvWalk, HEADER_SYNC_REFILL_MIN_INTERVAL_SECONDS, "inv-walk bridge");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright (c) 2026 The Triangles developers
|
||||
// Distributed under the MIT/X11 software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
#ifndef TRIANGLES_SYNCMANAGER_H
|
||||
#define TRIANGLES_SYNCMANAGER_H
|
||||
|
||||
#include "uint256.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
class CBlock;
|
||||
class CInv;
|
||||
class CNode;
|
||||
|
||||
class CSyncManager
|
||||
{
|
||||
public:
|
||||
struct HeaderNode;
|
||||
|
||||
static constexpr unsigned int HEADER_DOWNLOAD_WINDOW = 1024;
|
||||
static constexpr unsigned int HEADER_SYNC_LOW_WATER = HEADER_DOWNLOAD_WINDOW / 4;
|
||||
static constexpr unsigned int HEADER_SYNC_TARGET_INFLIGHT = HEADER_DOWNLOAD_WINDOW / 2;
|
||||
static constexpr int64_t HEADER_SYNC_REFILL_MIN_INTERVAL_SECONDS = 5;
|
||||
static constexpr int64_t HEADER_SYNC_CONTROL_INTERVAL_SECONDS = 5;
|
||||
static constexpr int64_t HEADER_SYNC_WATCHDOG_SECONDS = 25;
|
||||
|
||||
bool HaveHeader(const uint256& hash) const;
|
||||
uint256 GetBestHeader() const;
|
||||
std::size_t GetHeaderCount() const;
|
||||
unsigned int CountInFlight() const;
|
||||
unsigned int GetPlannerDepth() const;
|
||||
int GetPlannerHeight() const;
|
||||
int64_t GetRequestTime(const uint256& hashBlock) const;
|
||||
|
||||
bool RequestRefill(CNode* pfrom, uint256 hashTip, int64_t nMinIntervalSeconds, const char* pszReason);
|
||||
unsigned int RequestRefillAllPeers(uint256 hashTip, int64_t nMinIntervalSeconds, const char* pszReason);
|
||||
unsigned int QueueBlocksParallel(unsigned int nWindow = HEADER_DOWNLOAD_WINDOW);
|
||||
bool ProcessHeaders(CNode* pfrom, const std::vector<CBlock>& vHeaders);
|
||||
void BlockAccepted(const uint256& hashBlock);
|
||||
void TrackBlockDelivery(CNode* pfrom, const uint256& hashBlock);
|
||||
void Tick(CNode* pto, int nHighestInvWalk, const uint256& hashHighestInvWalk);
|
||||
|
||||
private:
|
||||
uint256 GetHeaderTrust(unsigned int nBits) const;
|
||||
bool GetKnownHeaderState(const uint256& hash, int& nHeight, uint256& nChainTrust) const;
|
||||
bool GetPrevHash(const uint256& hash, uint256& hashPrev) const;
|
||||
void RecomputeBestHeader();
|
||||
void PruneHeaders();
|
||||
bool AddHeaderNode(const CBlock& header, const uint256& hashHeader);
|
||||
std::vector<uint256> GetDownloadPath(uint256 hashTip) const;
|
||||
void ContinueHeaders(CNode* pfrom, const uint256& hashTip);
|
||||
};
|
||||
|
||||
extern CSyncManager g_syncManager;
|
||||
|
||||
#endif // TRIANGLES_SYNCMANAGER_H
|
||||
+2
-1
@@ -42,12 +42,13 @@ public:
|
||||
|
||||
bool LoadBlockIndex() override;
|
||||
|
||||
std::unique_ptr<CTxDBIteratorBase> NewIterator() const override;
|
||||
|
||||
protected:
|
||||
bool ReadRaw(const std::string& key, std::string& value) const override;
|
||||
bool WriteRaw(const std::string& key, const std::string& value) override;
|
||||
bool EraseRaw(const std::string& key) override;
|
||||
bool ExistsRaw(const std::string& key) const override;
|
||||
std::unique_ptr<CTxDBIteratorBase> NewIterator() const override;
|
||||
|
||||
private:
|
||||
leveldb::DB* pdb; // Points to the global instance.
|
||||
|
||||
+11
-1
@@ -38,12 +38,22 @@ public:
|
||||
|
||||
bool LoadBlockIndex() override;
|
||||
|
||||
// Write a raw serialized key/value pair, bypassing the typed Write<>()
|
||||
// overloads. Intended for the chaindb migration utility, which carries
|
||||
// bytes directly across from a CTxDB (LevelDB) iterator. Honors the
|
||||
// active write batch if one is open.
|
||||
bool WriteRawRecordForMigration(const std::string& key, const std::string& value)
|
||||
{
|
||||
return WriteRaw(key, value);
|
||||
}
|
||||
|
||||
std::unique_ptr<CTxDBIteratorBase> NewIterator() const override;
|
||||
|
||||
protected:
|
||||
bool ReadRaw(const std::string& key, std::string& value) const override;
|
||||
bool WriteRaw(const std::string& key, const std::string& value) override;
|
||||
bool EraseRaw(const std::string& key) override;
|
||||
bool ExistsRaw(const std::string& key) const override;
|
||||
std::unique_ptr<CTxDBIteratorBase> NewIterator() const override;
|
||||
|
||||
private:
|
||||
rocksdb::DB* pdb; // Points to the global instance.
|
||||
|
||||
@@ -600,6 +600,37 @@ bool SoftSetBoolArg(const std::string& strArg, bool fValue)
|
||||
return SoftSetArg(strArg, std::string("0"));
|
||||
}
|
||||
|
||||
// C++20 modernization: std::string_view overloads delegating to std::string implementations
|
||||
std::string GetArg(std::string_view strArg, std::string_view strDefault)
|
||||
{
|
||||
return GetArg(std::string(strArg), std::string(strDefault));
|
||||
}
|
||||
|
||||
int64_t GetArg(std::string_view strArg, int64_t nDefault)
|
||||
{
|
||||
return GetArg(std::string(strArg), nDefault);
|
||||
}
|
||||
|
||||
bool GetBoolArg(std::string_view strArg, bool fDefault)
|
||||
{
|
||||
return GetBoolArg(std::string(strArg), fDefault);
|
||||
}
|
||||
|
||||
bool SoftSetArg(std::string_view strArg, std::string_view strValue)
|
||||
{
|
||||
return SoftSetArg(std::string(strArg), std::string(strValue));
|
||||
}
|
||||
|
||||
bool SoftSetBoolArg(std::string_view strArg, bool fValue)
|
||||
{
|
||||
return SoftSetBoolArg(std::string(strArg), fValue);
|
||||
}
|
||||
|
||||
bool WildcardMatch(std::string_view str, std::string_view mask)
|
||||
{
|
||||
return WildcardMatch(std::string(str), std::string(mask));
|
||||
}
|
||||
|
||||
|
||||
string EncodeBase64(const unsigned char* pch, size_t len)
|
||||
{
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <string_view>
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
@@ -190,6 +191,9 @@ bool ATTR_WARN_PRINTF(1,2) error(const char *format, ...);
|
||||
#define printf OutputDebugStringF
|
||||
|
||||
void LogException(std::exception* pex, const char* pszThread);
|
||||
|
||||
// LogPrintf - variadic macro for logging to stderr (C++20 modernization: restored from removed definition)
|
||||
#define LogPrintf(...) fprintf(stderr, __VA_ARGS__)
|
||||
void PrintException(std::exception* pex, const char* pszThread);
|
||||
void PrintExceptionContinue(std::exception* pex, const char* pszThread);
|
||||
void ParseString(std::string_view str, char c, std::vector<std::string>& v);
|
||||
|
||||
+7
-6
@@ -12,6 +12,7 @@
|
||||
#include "kernel.h"
|
||||
#include "coincontrol.h"
|
||||
#include "addressindex.h"
|
||||
#include "util.h"
|
||||
#include <memory>
|
||||
#include <algorithm>
|
||||
#include <random>
|
||||
@@ -483,7 +484,7 @@ void CWallet::WalletUpdateSpent(const CTransaction &tx, bool fBlock)
|
||||
if (!IsInitialBlockDownload())
|
||||
{
|
||||
try { NotifyTransactionChanged(this, txin.prevout.hash, CT_UPDATED); }
|
||||
catch (...) { LogPrintf("WARNING: NotifyTransactionChanged exception in WalletUpdateSpent\n"); }
|
||||
catch (...) { printf("WARNING: NotifyTransactionChanged exception in WalletUpdateSpent\n"); }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -504,7 +505,7 @@ void CWallet::WalletUpdateSpent(const CTransaction &tx, bool fBlock)
|
||||
if (!IsInitialBlockDownload())
|
||||
{
|
||||
try { NotifyTransactionChanged(this, hash, CT_UPDATED); }
|
||||
catch (...) { LogPrintf("WARNING: NotifyTransactionChanged exception in WalletUpdateSpent\n"); }
|
||||
catch (...) { printf("WARNING: NotifyTransactionChanged exception in WalletUpdateSpent\n"); }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2162,7 +2163,7 @@ bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey)
|
||||
coin.MarkSpent(txin.prevout.n);
|
||||
coin.WriteToDisk();
|
||||
try { NotifyTransactionChanged(this, coin.GetHash(), CT_UPDATED); }
|
||||
catch (...) { LogPrintf("WARNING: NotifyTransactionChanged exception in CommitTransaction\n"); }
|
||||
catch (...) { printf("WARNING: NotifyTransactionChanged exception in CommitTransaction\n"); }
|
||||
}
|
||||
|
||||
if (fFileBacked)
|
||||
@@ -2292,7 +2293,7 @@ bool CWallet::SetAddressBookName(const CTxDestination& address, const string& st
|
||||
SecureMsgWalletKeyChanged(caddress.ToString(), strName, nMode);
|
||||
}
|
||||
try { NotifyAddressBookChanged(this, address, strName, fOwned, nMode); }
|
||||
catch (...) { LogPrintf("WARNING: NotifyAddressBookChanged exception in SetAddressBookName\n"); }
|
||||
catch (...) { printf("WARNING: NotifyAddressBookChanged exception in SetAddressBookName\n"); }
|
||||
|
||||
if (!fFileBacked)
|
||||
return false;
|
||||
@@ -2315,7 +2316,7 @@ bool CWallet::DelAddressBookName(const CTxDestination& address)
|
||||
SecureMsgWalletKeyChanged(caddress.ToString(), sName, CT_DELETED);
|
||||
}
|
||||
try { NotifyAddressBookChanged(this, address, "", fOwned, CT_DELETED); }
|
||||
catch (...) { LogPrintf("WARNING: NotifyAddressBookChanged exception in DelAddressBookName\n"); }
|
||||
catch (...) { printf("WARNING: NotifyAddressBookChanged exception in DelAddressBookName\n"); }
|
||||
|
||||
if (!fFileBacked)
|
||||
return false;
|
||||
@@ -2794,7 +2795,7 @@ void CWallet::UpdatedTransaction(const uint256 &hashTx)
|
||||
if (auto mi = mapWallet.find(hashTx); mi != mapWallet.end() && !IsInitialBlockDownload())
|
||||
{
|
||||
try { NotifyTransactionChanged(this, hashTx, CT_UPDATED); }
|
||||
catch (...) { LogPrintf("WARNING: NotifyTransactionChanged exception in UpdatedTransaction\n"); }
|
||||
catch (...) { printf("WARNING: NotifyTransactionChanged exception in UpdatedTransaction\n"); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user