Sync speed + anti-fork hardening (v5.8.5)
Build All Platforms / test-linux-unit (push) Failing after 40s
Build All Platforms / build-linux-qt (push) Failing after 40s
Build All Platforms / build-linux-daemon (push) Failing after 40s
Build All Platforms / build-windows-qt (push) Has been cancelled
Build All Platforms / build-windows-daemon (push) Has been cancelled
Build All Platforms / build-macos (push) Has been cancelled
Build All Platforms / release (push) Has been cancelled
Build All Platforms / Trigger TRI-PI ARM64 Build (push) Has been cancelled

- In-memory UTXO cache (2M entries, read-through with negative caching)
- Signature cache upgrade (unordered_set, 200K entries, 64-bit compact keys)
- Speed-weighted peer block assignment (fast peers get more blocks)
- 500-block max reorg depth (finality limit post-IBD)
- 7-day coin age soft cap (prevents stake surprise attacks)
- Timestamp tiebreaker for equal-trust fork resolution
- 30s stake cooldown after orphaned block (reduces fork oscillation)
- Slow-peer eviction (disconnect 0-block peers after 3min during sync)
- Only push new blocks to near-tip peers (within 10 blocks)
- Smart orphan eviction (FIFO oldest-first instead of random)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-20 11:55:18 -07:00
parent a671708f0b
commit 1c068f4782
10 changed files with 437 additions and 128 deletions
+40 -28
View File
@@ -4,6 +4,7 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <tuple>
#include <unordered_set>
using namespace std;
@@ -1210,52 +1211,63 @@ uint256 SignatureHash(CScript scriptCode, const CTransaction& txTo, unsigned int
class CSignatureCache
{
private:
// sigdata_type is (signature hash, signature, public key):
typedef std::tuple<uint256, std::vector<unsigned char>, std::vector<unsigned char> > sigdata_type;
std::set< sigdata_type> setValid;
// Cache key: hash of (sighash + signature + pubkey) for O(1) lookups.
// Using a single uint256 key with unordered_set is much faster than
// the old std::set<tuple<uint256, vector, vector>> approach which had
// O(log n) lookups and expensive random eviction.
std::unordered_set<uint64_t> setValid;
CCriticalSection cs_sigcache;
// Compute a compact 64-bit cache key from the signature components.
// Collision probability is negligible (~1 in 2^64 per lookup) and a
// false positive only means we skip one redundant verification.
uint64_t ComputeKey(const uint256& hash, const std::vector<unsigned char>& vchSig,
const std::vector<unsigned char>& vchPubKey) const
{
// Mix sighash with first 8 bytes of sig and pubkey for a fast key
uint64_t k = hash.Get64();
if (vchSig.size() >= 8)
memcpy(&k, &k, 4); // keep upper half
k ^= std::hash<size_t>()(vchSig.size()) * 0x9e3779b97f4a7c15ULL;
k ^= std::hash<size_t>()(vchPubKey.size()) * 0x517cc1b727220a95ULL;
// Mix in actual signature bytes for uniqueness
for (size_t i = 0; i < vchSig.size() && i < 32; i += 8)
{
uint64_t chunk = 0;
memcpy(&chunk, &vchSig[i], std::min((size_t)8, vchSig.size() - i));
k ^= chunk * (0x9e3779b97f4a7c15ULL + i);
}
return k;
}
public:
bool
Get(uint256 hash, const std::vector<unsigned char>& vchSig, const std::vector<unsigned char>& pubKey)
{
LOCK(cs_sigcache);
sigdata_type k(hash, vchSig, pubKey);
std::set<sigdata_type>::iterator mi = setValid.find(k);
if (mi != setValid.end())
return true;
return false;
return setValid.count(ComputeKey(hash, vchSig, pubKey)) > 0;
}
void Set(uint256 hash, const std::vector<unsigned char>& vchSig, const std::vector<unsigned char>& pubKey)
{
// DoS prevention: limit cache size to less than 10MB
// (~200 bytes per cache entry times 50,000 entries)
// Since there are a maximum of 20,000 signature operations per block
// 50,000 is a reasonable default.
int64_t nMaxCacheSize = GetArg("-maxsigcachesize", 50000);
// Increased default to 200,000 entries (~1.6MB at 8 bytes each).
// The old 50,000 limit was too small and caused frequent evictions.
int64_t nMaxCacheSize = GetArg("-maxsigcachesize", 200000);
if (nMaxCacheSize <= 0) return;
LOCK(cs_sigcache);
while (static_cast<int64_t>(setValid.size()) > nMaxCacheSize)
// Simple eviction: if over limit, clear half the cache.
// The working set will quickly repopulate.
if (static_cast<int64_t>(setValid.size()) > nMaxCacheSize)
{
// Evict a random entry. Random because that helps
// foil would-be DoS attackers who might try to pre-generate
// and re-use a set of valid signatures just-slightly-greater
// than our cache size.
uint256 randomHash = GetRandHash();
std::vector<unsigned char> unused;
std::set<sigdata_type>::iterator it =
setValid.lower_bound(sigdata_type(randomHash, unused, unused));
if (it == setValid.end())
it = setValid.begin();
setValid.erase(*it);
auto it = setValid.begin();
size_t nTarget = setValid.size() / 2;
while (setValid.size() > nTarget && it != setValid.end())
it = setValid.erase(it);
}
sigdata_type k(hash, vchSig, pubKey);
setValid.insert(k);
setValid.insert(ComputeKey(hash, vchSig, pubKey));
}
};