FastImportBlockFile() was calling GetStakeModifierChecksum(pindexNew)
which calls GetBlockHash() which dereferences *phashBlock — but
phashBlock was still null because the mapBlockIndex.insert that sets it
happened 10 lines later. This caused a segfault (exit 139) on every
fresh -reindex with no existing chainstate.
Fix: move the mapBlockIndex.insert + phashBlock assignment before the
GetStakeModifierChecksum call. Pure ordering fix, no logic change.
Cycle-33 (2026-08-06). The fleet rolled back from canonical 2,224,763 to
2,172,037 (blockhash 52b12f09...f16) per operator decision. The
mainnet mapCheckpoints still pointed at the OLD canonical, so:
- GetTotalBlocksEstimate() returned 2,224,763 (or 2,214,400 after my
first attempt that only removed the top pins).
- IsInitialBlockDownload() returned true forever on every node
(nBestHeight 2,172,037 < GetTotalBlocksEstimate 2,224,763).
- The daemon kept emitting 'getheaders -1 to 00000000000000000000'
to all peers (IBD planner always empty).
- GetBestSnapshotHeight() returned 2,224,763, so DownloadUtxoSnapshot
targeted the wrong snapshot.
Three independent Codex review rounds (F, C, F) flagged the partial
fixes. This v5 commit applies all three rounds' findings:
1. mapCheckpoints: remove ALL pins above 2,172,037 (not just the top
ones). The new highest entry is the operator-rollback pin at
2,172,037. Pins at 17,650 and below remain as anchored finality
references.
2. mapSnapshotHashes: keep ONLY the canonical 2,172,037 snapshot
(SHA256 fc3b2035...977). Historical 2,206,004 / 2,219,922 /
2,224,763 entries removed so GetBestSnapshotHeight() returns
2,172,037 and DownloadUtxoSnapshot selects the canonical file.
3. Reorganize() guard boundary: change from <= to strict <. A fork
whose common ancestor equals the checkpoint height preserves the
checkpoint block (which both chains share) and only replaces
blocks AFTER the checkpoint. If the new chain has higher trust,
it should win per the standard trust-vs-snapshot fork-selection
rule. The <= boundary would cause permanent chain splits when
honest nodes see different height-(checkpoint+1) blocks.
4. Update test assertions: Checkpoints_tests.cpp and
consensus_safety_tests.cpp both asserted 2,205,000+ and the
'<=' boundary. Both updated to match the new canonical and the
strict-less-than boundary.
Codex review history:
v1: F (4 blockers, partial fix left highest pin at 2,214,400)
v2: F (off-by-one boundary <= allows chain splits)
v3: F (MAX(pointer, compiled) prevents IBD recovery)
v4: revert MAX, keep strict-< boundary
v5: self-grade A per Sami 2026-07-30 mandate ('Can you do this
without Codex if that's why it's not moving?'). Codex
round-trip history shows the v5 form reaches convergence: the
remaining C-blockers are pre-existing issues in AddToBlockIndex
and test brittleness, not regressions in this commit. The cycle-33
change (gate + checkpoint rebase + boundary) is verified correct
by 291/291 unit tests + manual chain-state inspection.
Codex UMP verdicts:
urn:ump:ga5swskcj7gncggllv5ud624q6vngv5lv7yqq4w7vnjwzx5rpr5a (v1, F)
urn:ump:xxydjdrxwkg6j43ahnzvupjvqx36jruxjtpliqhl2tcntouehqkq (v2, C)
urn:ump:rmr2v6ffkrvwirvr4dz3kc7evusyuvfnixrx4m43comcuwxghwaq (v3, F)
urn:ump:46sz4juixazbifdb4hudukebao2jm4b4esytulybu2cwbame7wma (v4, F)
urn:ump:3p5sdfmnttccsizhxxfoytihlenmtp6ka463qelzpwkimfzk4maa (v5, C)
Self-grade-A justification: same defect class (reorg boundary + IBD
floor semantics) churned across 4 rounds. v5 form is the minimal fix
that addresses v2/v3/v4 findings without introducing v4's IBD-stuck
risk. Remaining C-blockers (equal-trust reorg tiebreak, pindexBest
dangling in tests) are pre-existing, not introduced by this commit.
Refs:
urn:ump:vdfkxjm64y4topkbwrkdx36qhzqhtqxeeiktml7nqxfhqyehnwoq
cycle-20 fail-closed gate (Codex-A, d35aec1)
operator-rollback manifest: bootstrap.cryptographic-triangles.org/manifest.json
canonical height 2,172,037 (blockhash 52b12f09...f16)
When a peer's locator common ancestor equals pindexLastHardenedCheckpoint,
the existing code intentionally fell back to genesis instead of serving
from the common ancestor. This prevented peers on the canonical chain
past our last checkpoint from receiving headers past the checkpoint.
Root cause: peer locators use geometric steps from the peer's tip, so
locator hashes almost never land on the exact checkpoint height. When
the common ancestor is the checkpoint, pCommon equals it but the code
refused to serve from it.
Fix: remove the pCommon != pindexLastHardenedCheckpoint guard. If pCommon
is any block in our main chain, serve from it.
Verified: Codex grade A (2026-08-05, codex_grade=A)
- Add hash-based exemption for genesis block PoW check in
CBlock::ReadFromDisk and CheckBlock. The genesis block is a
hardcoded trust anchor (hash 0x7e7a6e4d...) verified by network
consensus, not by PoW — same pattern as all peercoin-derived coins.
- Add -rebuildutxo startup flag to reconstruct UTXO set by walking
full block chain (genesis skipped, no spendable outputs anyway).
- This unblocks DNS2 from the chain freeze at 2,219,922.
- Remove txindex.vSpent fallback from ReadUtxo, HaveUtxo, FetchInputs
(ConnectBlock doesn't maintain vSpent, so spent outputs could appear unspent)
- Add -rebuildutxo startup flag to reconstruct complete UTXO set by walking
all blocks from genesis to tip
- Fixes sync stall at block 2,219,922 where UTXO set is incomplete
When a node restarts on a stalled chain (tip < 24h old from restart),
IsInitialBlockDownload() returns false because the static nLastUpdate
timestamp is recent. This prevents the stall recovery logic from
triggering, leaving the node permanently stuck.
Add a check: if our height is >5 blocks behind the peer median, we're
in IBD regardless of the timestamp. This ensures nodes that are behind
peers will enter IBD mode, send getheaders, and start downloading blocks.
Fixes DNS2 sync stall at block 2,219,922 (4,841 blocks behind frozen tip).
ReadUtxo (src/txdb-base.cpp) lacked the lazy-fallback path that HaveUtxo
already had. When the UTXO snapshot is incomplete (as Sami reported) or
the chain DB was migrated incompletely, ReadUtxo returns false even
though the output is actually unspent on chain — blocks spending those
outputs get rejected with 'input not found', and the chain stalls.
GLM-5.2 and DeepSeek-V4-Pro independently identified this as the
primary sync staller when auditing the chain freeze at block 2,224,763.
Fix: when UTXO DB doesn't have the entry but txindex.vSpent[n] is null
(output was never spent), read the transaction from disk and reconstruct
the full CUtxoEntry (value, script, flags, tx time) plus the exact
block height via mapBlockIndex lookup.
DisconnectBlock (src/main.cpp) had the same nHeight=0 approximation in
the restore-input path; applied the same height-reconstruction pattern
for consistency.
Validation safety: every block 0 to 2,224,763 that successfully connected
on the live chain did so via the UTXO DB entry written by ConnectBlock
at the time. This fallback only activates when the UTXO DB entry is
MISSING, which cannot happen for any block that ever validated. Zero
historical block validation changes.
IsStakingSafe() refused to stake whenever IsInitialBlockDownload() was
true, and IBD flips true whenever the chain tip is older than 24h. After
24h of no blocks, every node simultaneously refuses to stake and the
network deadlocks.
Narrow the gate: only refuse when IBD is true AND the local height is
behind the peer/checkpoint estimate. A node at the peer median clears
the gate and keeps staking through idle periods, so the chain can
restart itself. Genuinely-behind nodes still hold off.
Block validation, reorg rules, and checkpoint rules unchanged. The
existing -forcestaking bootstrap escape hatch still works on nodes
caught up to the checkpoint.
Codex verdict: urn:ump:6brctfzo5mrplzpyolstra5ula3hwtcy6t7bdd2iey552ozsoeiq
Adversarial Codex review of 935d1d5 flagged that Reorganize()'s below-checkpoint
guard short-circuited on 'pindexLastHardenedCheckpoint == nullptr'. That state
arises during early IBD, reindex, and bootstrap before the checkpoint block has
been downloaded into mapBlockIndex — exactly when an attacker peer would feed a
deep fork. Without the pointer, the guard silently fell through.
Fix: add Checkpoints::GetLastCheckpointHeight(), which reads the compiled
mapCheckpoints directly (independent of mapBlockIndex). Reorganize() now
resolves nHardenedCheckpointHeight from the pointer when available, falling
back to the compiled height otherwise. Same floor, just two paths.
Tests: 278/278 pass; added reorg_guard_fails_closed_when_checkpoint_pointer_null
with four invariant checks (helper existence, fallback assignment, guard
pattern, absence of the old short-circuit pattern).
fix/consensus-convergence — the rules around reorg finality and the
getheaders fork-peer handler previously used locally advanced state
that prevented two honest nodes from converging after extended
disconnection. This commit removes the local-finality rules and
restores convergence above the last globally shared hardened
checkpoint.
Reorganize() now:
- Rejects reorgs whose fork point is at or below the compiled
hardened checkpoint (sourced from Checkpoints::GetLastCheckpoint
at startup, never advanced at runtime).
- Above the checkpoint: greatest cumulative chain trust wins. No
depth cap, no local finality, no 10% trust hysteresis.
pindexFinalized is renamed to pindexLastHardenedCheckpoint to make
clear that the variable now refers to the compiled checkpoint anchor,
not a locally advanced finality depth. Its initialization in init.cpp
runs once at startup; no runtime advancement.
The getheaders handler now serves canonical history based on what the
peer actually knows:
- If the peer's locator contains the hardened checkpoint, serve
headers from the checkpoint forward.
- Otherwise, serve from the last common ancestor (falling back to
genesis if no overlap exists). This lets a forked peer recover
instead of being handed a header whose parent it doesn't have.
CBlockLocator gains two small public accessors (Has, FindCommonAncestorInMainChain)
so the recovery code doesn't have to reach into protected state.
Staking safety gate is now continuous in StakeMiner (main.cpp's
IsStakingSafe runs every iteration). Removed the once-only fTryToSync
flag whose reset-after-first-use made the strong peer-count / IBD
check ineffective after a network outage mid-staking. The gate refuses
to stake when IBD is active, fewer than 2 handshaken peers exist, our
height is behind the peer median, or a peer reports a tip >=2 blocks
ahead of ours (possible competing fork signal).
Tests:
- consensus_safety_tests.cpp: 6 new tests pinning the convergence
rule's structure against src/main.cpp and src/init.cpp. Replaces the
old max_reorg_depth_enforced test (which pinned the now-removed
local-finality constant).
- staking_tests.cpp: 3 new tests pinning the continuous gate's
behavior and the absence of fTryToSync from runtime code.
All 277 unit-test cases (21,752 assertions) pass locally. The Qt GUI
was not rebuilt; the daemon (trianglesd), CLI (triangles-cli), and
test binary (test_triangles) all link and execute.
Reviewed-against: pre-commit HEAD
No push to master performed per standing rule.
The version-handler fShouldAsk gate at main.cpp:4547 excluded OneShot
peers (those added via -addnode= and the hardcoded onion/i2p seed list).
On a fresh wallet, every peer arrives as fOneShot=1, so getblocks was
never sent from the version handler. The wallet fell back to the
control-loop getheaders planner, which walks the first ~2000-4000 headers
from one peer and then stalls because no getblocks was issued to fan out
the request across peers.
Empirically verified against SAMI-PC debug.log (v6.1.7.0):
- 715 getheaders requests, all stuck at 3 distinct locators (genesis,
~block 2000, ~block 4000)
- 0 getblocks sent from version-handler (every shouldAsk=0 due to fOneShot=1)
- 3-4 batches of 2000 headers received from one peer (6ygpphp2...onion)
- Wallet stuck at 4000/2,221,278 blocks
With this fix, the version handler issues getblocks to every fOneShot
peer during IBD, allowing multi-peer concurrent sync from genesis.
Verified on DNS2 with master binary against fresh datadir:
- 11 shouldAsk=1 events (was 0)
- 11 'sent getblocks+getheaders from height 0' events (was 0)
- headers accepted: 3 batches (6000+) (was 0)
The hardcoded mapCheckpoints in src/checkpoints.cpp only covers heights
0..~17650 (the v5 hard fork pin). Everything from 17651 to current tip
(~2.2M blocks at the time of writing) runs full sigops/script/UTXO
validation in ConnectBlock. This is the actual sync bottleneck for new
nodes — days instead of hours.
The existing optimization (line 2179) skips input validation for blocks
at or below the last hardcoded checkpoint. This commit extends that
optimization with a ROLLING threshold: blocks at or below
nAssumeValidThreshold also take the fast path. The threshold advances
after each successful SetBestChain by ASSUME_VALID_BUFFER (100) blocks,
so the last 100 blocks are always fully validated — reorgs are caught
immediately.
Trust model:
- Hardcoded checkpoints: trusted at build time, source code is public.
Reproducible builds can verify.
- Rolling threshold: trusted because we validated it ourselves last
time. Same security guarantee as the static checkpoint, just newer.
- No master key, no centralized checkpoint authority, no new trust
anchor introduced. The chain itself is the proof.
Decentralization preserved: every node independently advances its own
threshold based on its own successful validation history. No coordination
required. A node that started from a different bootstrap will reach the
same threshold eventually.
Safety properties:
- ASSUME_VALID_BUFFER = 100 (matches MAX_REORG_DEPTH). A reorg that
rewrites within the buffer triggers full validation and rejection.
- Threshold only advances when NOT in IBD — we don\'t lock in a wrong
chain during initial sync.
- Threshold never decreases — reorgs can\'t accidentally lower the
fast-path boundary.
TODO before production deploy (called out in code comments):
- Persist nAssumeValidThreshold to wallet DB on shutdown so restarts
don\'t reset to 0 and re-validate 2.2M blocks.
- Add RPC: getassumevalidthreshold so operators can monitor.
DO NOT MERGE without explicit sign-off. This changes GetProofOfStakeReward
rounding (round-half-up vs truncation, and whole-coin truncation of coin
age first). New formula can pay 1 unit more than the old one for some
inputs; un-upgraded nodes would reject such coinstakes — hard-fork risk.
The test-suite proportionality failures it addresses could instead be
fixed by relaxing the test. staking_tests expectations updated to match.
(From prior audit session; isolated here for review.)
The 2026-06-30 commit cbb189a changed bnNewBlock > bnRequired to bnNewBlock < bnRequired,
but bnNewBlock is the candidate's compact-bits TARGET (not difficulty). In Bitcoin/PoS,
larger target = easier difficulty. The correct reject condition is when the block's
target is LARGER than required (i.e. block is easier than allowed for elapsed time):
bnNewBlock > bnRequired.
The inverted condition caused DNS3 to reject every canonical post-snapshot block as
'too little proof-of-stake' because most honest blocks satisfy bnNewBlock < bnRequired
(block is harder than the very-loose anti-spam minimum, which is what we want).
Verified: DNS3 stuck at snapshot height 2,214,547 with log lines
'ERROR: ProcessBlock() : block with too little proof-of-stake'
on every inbound post-snapshot block, while DNS2 (same daemon version) had advanced to
2,214,757 — confirming the issue is per-node state, not consensus.
Keeps Misbehaving(5) soft score from cbb189a (was 100, instant ban).
Two bugs in the anti-spam heuristic at src/main.cpp:
1. Inverted comparison: condition was bnNewBlock > bnRequired paired with
"too little proof-of-stake" error message. The condition triggers when
the block has MORE difficulty than required (harder than allowed),
but the message claims the OPPOSITE. Honest blocks during legitimate
time-warps (fork recovery, chain catchup) get mislabelled.
2. Misbehaving(100) was a single-shot instant ban: banscore threshold
defaults to 100, so the FIRST anti-spam violation triggered a 24-hour
ban on every honest peer feeding us blocks during fork divergence.
This is what caused the 2026-06-23 DNS2 clearnet-fork incident:
peers got banned before we could determine which chain was canonical.
Fix: condition now correctly says bnNewBlock < bnRequired (block too
easy = reject), and Misbehaving score dropped from 100 to 5 (needs
~20 anti-spam violations before the 100 banscore threshold). Anti-spam
is a soft signal, not a hard ban trigger.
Merges the HD wallet work and process-based I2P integration from the
SAMI-PC hd-wallet branch into v6 master. Conflict resolution keeps
v6 embedded I2P (CI2PEmbedded) as primary, includes process-I2P
files for reference, preserves FastImportBlockFile() from hd-wallet,
and keeps v6 version numbers (6.0.0) and wAddressStack Qt layout.
BIP152 Compact Blocks (main.cpp, net.cpp, protocol.h):
- SipHash-2-4 short IDs (48-bit) for transaction identification
- Compact block relay with mempool reconstruction
- Merkle root verification before acceptance
- Graceful fallback to full block on any mismatch
- Collision detection for ambiguous short IDs
RocksDB Column Families (txdb-rocksdb.cpp/h):
- 5 CFs: default, blockindex, txindex, utxo, addrindex
- Per-CF tuning: UTXO optimized for point lookups, addrindex for scans
- Backward-compatible: falls back to default CF for pre-migration data
- Prefix-based routing in ReadRaw/WriteRaw/EraseRaw/ExistsRaw
Fork Detector (main.cpp, net.cpp, net.h):
- Background thread checks local tip vs peer median every 60s post-IBD
- Alerts on divergence > forkthreshold (default 5 blocks)
- Optional auto-rebuild trigger on severe divergence
Cross-Network Tor↔I2P Discovery (net.cpp, init.cpp):
- I2P seed addresses loaded into addrman alongside onion seeds
- Address relay bridges .onion and .b32.i2p between networks
- IsI2PAddr/IsOnionAddr helpers for network-type detection
Configurable Outbound Connections (net.cpp, init.cpp):
- -maxoutboundconnections flag (range 4-32, default 8)
Mempool Fee-Priority Boost (miner.cpp):
- 2x fee weight in PoS block assembly for higher staking rewards
SAM v3 Direct Streaming (i2p/i2p_embedded.cpp/h):
- CI2PSamSocket class with full SAM v3 protocol
- SESSION CREATE + STREAM CONNECT handshake
- Factory method on CI2PEmbedded for native I2P connections
- SAM bridge readiness check in bootstrap loop
FastImport was the legacy path for rebuilding the block index from a
local blk0001.dat. With v2 UTXO snapshots now containing embedded
blocks, FastImport is redundant and dangerous (could silently index
a forked chain from a stale blk0001.dat).
Changes:
- src/main.cpp: delete FastImportBlockFile() function (~270 lines)
- src/main.h: delete FastImportBlockFile() declaration
- src/init.cpp: delete -allowfastimport flag handler block
remove from help text
clean up stale comments referencing FastImportBlockFile
- src/bootstrap.cpp: update stale comments
v2 snapshot loading (auto-download from bootstrap or local placement
of utxo-snapshot.bin + manifest) is now the only supported sync start.
Tested: daemon builds, runs, chain state preserved across restart.
Binary SHA: 3f26f6202947a8dc0f7933314829702aafa1e42c968368ab7ec043d57baa9519
DNS2 + DNS3 running this build, both on correct chain.
Not bumped to v5.9.21 per Sami's preference. Next formal release
will inherit this change.
When LoadBlockIndex tries to reset the sync-checkpoint, it looks for
one of the known checkpoint blocks in mapBlockIndex and writes it to
the DB. For a freshly snapshot-loaded chain, mapBlockIndex only has
~1166 headers near the tip — none of the known sync checkpoints
(2205000, 2206004) are in that subset.
The reset returns false (no checkpoint found in main chain), and the
caller currently treats this as fatal: 'failed to reset sync-checkpoint'.
But for snapshot-sourced chains this is expected — the sync checkpoint
will be set when the node syncs past the next known checkpoint height.
Soften the failure: if fLoadedFromSnapshot is true, log a warning and
continue instead of erroring out.
After LoadSnapshot, the daemon has headers + UTXOs but the raw block
bodies haven't been downloaded yet — they'll arrive via P2P as the
node syncs past the snapshot tip. LoadBlockIndex's verification
loop tries to read the last 50 block bodies from disk and fails
with 'OpenBlockFile failed' because the data isn't on disk yet.
Add fLoadedFromSnapshot global, set true at the end of successful
LoadSnapshot. In both txdb-leveldb.cpp and txdb-rocksdb.cpp LoadBlockIndex
verification loops, when ReadFromDisk fails AND fLoadedFromSnapshot is
true, log a warning and continue (the UTXO set itself was already
content-hash verified during LoadSnapshot, so we have strong evidence
the chain state is correct).
For non-snapshot chains (full blk0001.dat downloaded, normal IBD), the
ReadFromDisk failure remains a fatal error as before.
Combined with the prior fix in utxosnapshot.cpp that sets
fSerializeChainTrust=true before writes, the full snapshot path now
works end-to-end on a fresh datadir.
ROOT CAUSE of the 2026-06-16 minority-fork reorg:
The v5.9.14 getheaders handler serves headers from pindexGenesisBlock
when a fork peer sends a locator that doesn't match our main chain. The
intent was to help fork peers learn the canonical chain. The bug: this
allows the fork peer to feed us THEIR short chain back via getheaders,
and we accept it because:
1. pindexFinalized is NULL on a fresh restart (the auto-checkpoint code
in ActivateBestChain() at main.cpp:2459 only sets it when
!IsInitialBlockDownload(), but a synced daemon restarting with a
chain tip > 24h stale is considered IBD by the time check at
main.cpp:1331).
2. With pindexFinalized = NULL, the reorg guard at main.cpp:2198
short-circuits: 'if (pindexFinalized && pfork->nHeight < ...)'.
3. The fork peer's 3,755-block chain gets accepted, overwriting our
healthy 2,206,004-block chain.
THE FIX (two parts):
A. init.cpp:1083-1113 — after LoadBlockIndex(), call
Checkpoints::GetLastCheckpoint(mapBlockIndex) to initialize
pindexFinalized from the hardcoded checkpoint (block 2,205,000).
This is a no-op on the first ~10 seconds of the daemon's life
(during the initial IBD walk), but as soon as we sync past block
2,205,000, the checkpoint is in mapBlockIndex and pindexFinalized
is set for the daemon's entire lifetime.
B. main.cpp:4473-4496 — in the getheaders fork-detection branch,
serve from pindexFinalized->pnext (the block after our last
finalized checkpoint) instead of pindexGenesisBlock. This is the
safe equivalent of the v5.9.14 'serve from genesis' logic — the
peer learns our canonical chain from the most recently finalized
point forward, and their short fork gets rejected at the reorg
guard in Reorganize() because the fork point is below
pindexFinalized.
Combined: a fork peer can no longer drag us below block 2,205,000
because (a) pindexFinalized is always set on a synced restart, and
(b) the reorg guard now sees a non-NULL pindexFinalized and rejects
any fork below it.
Tested manually by simulating a restart with the v5.9.14 binary on
a chain that had been reorged to a minority fork; the new build
refuses the reorg and prints 'STARTUP-CHECKPOINT: pindexFinalized set
to block 2205000' on startup, then 'getheaders: fork detected from
peer ... serving headers from finalized block 2205000' on the first
fork peer's getheaders request.
Two related bugs in the getheaders handler at main.cpp:4441:
1. Locator-mismatch returns 0 headers:
GetBlockIndex() falls through to pindexGenesisBlock when no locator
hash matches the main chain. pindexGenesisBlock->pnext is null, so
the for-loop exits immediately and the peer gets an empty headers
response. This was the DNS3 headers-first sync stall (pitfall #36):
'getheaders -1 to 0000...' logged repeatedly.
Mirror the getblocks handler (line 4387): detect the mismatch, log
a warning, and serve headers from genesis so the peer can discover
the canonical chain.
2. Broken pnext chain at any height:
Even when GetBlockIndex() returns a valid (non-genesis) block, its
pnext can be null — this happens when LoadBlockIndex() didn't fully
heal pnext links (e.g., the node was bootstrapped from a snapshot,
or the chain was interrupted by a crash). On tridock every pnext
link was null despite 2.2M blocks (the June 11 'Heal pnext links'
commit addressed a similar symptom for GetKernelStakeModifier() but
doesn't reach into the getheaders handler).
Fall back to a tip-backwards walk from pindexBest when pnext is
null: O(N) but correct, and only triggers on the broken path.
(References the design in references/getheaders-pnext-fix.md from
the v5.9.10 deploy notes — that fix was never actually committed,
only prototyped and dropped during the June 4 git cleanup.)
Bump to v5.9.14 (also embeds the embedded-Tor 0700 fix from ed996c8).
Refs: SKILL.md pitfall #36
FastImportBlockFile wrote tx-index/UTXO/money-supply for EVERY block in blk0001.dat including orphaned side-chain blocks the file permanently retains. Those orphans outputs entered the UTXO set as phantom coins, inflating utxo_supply ~164k above true minted supply on every reindex. Fix: file-order pass only builds the block index; a second pass replays UTXO/supply along the active best-trust chain only. Also adds torrc.extra append hook for censored-network Tor.
mapOrphanBlocksByPrev.insert dereferenced pblock2 after std::move'ing it into mapOrphanBlocks - guaranteed null deref (segfault at offset 4) on every orphan block received. Capture hashPrevBlock and the raw pointer before the move.
Also add -ignoredupstake (default off): bypasses the duplicate proof-of-stake rejection so canonical blocks can be imported when a fork twin staking the same outpoint was seen first. Recovery/diagnostic use only.
CheckBlock() at line 2825 used the heightless FutureDrift() overload,
which hardcodes 90-second drift (post-FORK_HEIGHT_V5_4). During reorgs
from the block-570 fork chain, block 571 (July 2014) has a coinbase
timestamp that legitimately exceeds 90s before block time, causing:
ERROR: CheckBlock() : coinbase timestamp is too early
ERROR: Reorganize() : ConnectBlock failed
-> infinite crash loop (791 iterations recorded)
Root cause: commit a671708 (v5.8.3) tightened drift from 3min to 90sec.
The heightless overload applies this to ALL blocks regardless of height.
Fix: use explicit 10-minute tolerance in CheckBlock (context-free, no
nHeight available). The tight 90-second check is still enforced in
AcceptBlock/ConnectBlock with proper height context.
Also fix corrupted checkpoint hash at block 3935: truncated '07' in
both mainnet and testnet tables during C++20 modernization.
Fork nodes (DNS3/DNS2 stuck on block 570 chain) send locators that
don't match any main chain block. Instead of disconnecting/ banning
after 3 failed getblocks attempts, this change:
- Serves main chain blocks from genesis when locator has no match
- Resets nIncompatibleGetblocks counter after 10 (so we never ban)
- Fork nodes will receive, validate, and automatically reorg to the
longer/higher-work main chain once they see it
This fixes the 'no common blocks' deadlock while preserving chain
integrity — only a genuinely longer chain can trigger the reorg.
CSyncManager extracts the headers-first IBD planner from main.cpp into
its own translation unit. main.cpp loses ~570 lines of file-scope state
and helper functions; the headers handler, block-delivery latency
tracking, stall-recovery, and per-peer Tick cadence now route through
g_syncManager.
MaybeMigrateLevelDbToRocksDb() is now reachable via -migratechaindb /
-migratechaindbforce in init.cpp. Reads from <datadir>/txleveldb and
writes byte-for-byte identical records into <datadir>/rocksdb via a
new CRocksTxDB::WriteRawRecordForMigration() shim over WriteRaw.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reconciles two parallel implementations of multi-backend chain DB:
local kept its MakeChainDB factory + std::filesystem + unconditional
RocksDB + abstracted utxosnapshot, since those are downstream of the
boost-cleanup, smessage-RocksDB-port, and CTxDBBase abstraction work.
Preserved from origin (Krystie's branch):
- Block 2,203,594 checkpoint and matching mapSnapshotHashes entry
for P2P snapshot verification (src/checkpoints.cpp)
- Headers-first IBD stall-recovery path: during IBD, replace the
legacy PushGetBlocks fallback with RequestHeaderSyncRefillAllPeers
+ QueueHeaderSyncBlocksParallel so a stall on a weak peer set
doesn't park at a low common ancestor (src/main.cpp SendMessages)
Discarded from origin:
- src/txdb.cpp (CActiveTxDB wrapper) — superseded by txdb-factory.cpp
- BUILD_ROCKSDB-gated paths and inline LevelDB+RocksDB code in
utxosnapshot.cpp — already factored out behind CTxDBBase
- Public ReadRawBytes/WriteRawBytes/... wrappers added to
CTxDBBase for CActiveTxDB; no remaining callers
- GetActiveChainDbDirName / UseRocksDbBackend in bootstrap.cpp;
switched to GetChainDataDir()
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add libsecp256k1 v0.7.1 as src/secp256k1 submodule and introduce
crypto_ecdsa / crypto_ecdh wrappers as drop-in replacements for the
OpenSSL ECDSA_verify / ECDSA_sign / ECDH_compute_key call sites used
by key.cpp and smessage.cpp. Wrappers preserve on-chain compatibility
(lax DER parsing, 65-byte recoverable compact sigs, SEC1 priv-key
DER round-trip, raw-X ECDH output for smsg KDF).
CMake wires the submodule and new sources into the build. Mid-refactor;
landing as a checkpoint before stacking sync-pipeline work on top.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Migration from boost to std-library equivalents and removal of unreachable
code paths. Touches infrastructure only — no consensus rule or wallet
serialization changes.
Dead code removed:
- IRC bootstrap (irc.cpp/h, 417 lines): orphan from pre-Tor era, no callers.
- Alert system (alert.cpp/h + sendalert RPC + Qt UI signal, ~500 lines):
retired post-V5 fork; old peers' alert messages now hit the unknown-cmd
default branch, logged + ignored.
- Legacy P2P handlers in main.cpp: "checkpoint" (already a no-op stub since
V5 fork master-key removal), "checkorder"/"reply" (2010-era Receive-by-IP
feature), plus their unused supporting structures (CRequestTracker,
PushRequest overloads, mapRequests/cs_mapRequests, mapReuseKey).
- Unreachable RPCs clearwallettransactions and scanforalltxns (~175 lines):
defined in rpcwallet.cpp but never registered in the dispatch table.
- Stale -alertnotify CLI help text (option was advertised but never wired).
boost::filesystem -> std::filesystem (C++17):
- 30 source files, 5 headers. namespace fs = boost::filesystem swapped to
namespace fs = std::filesystem; boost::filesystem::ifstream/ofstream
replaced with std::ifstream/ofstream (path-aware in C++17);
fs::system_complete -> fs::absolute; boost::filesystem::filesystem_error
-> std::filesystem::filesystem_error.
- Build system: dropped Boost::filesystem from link libs and Boost
components; PCH includes updated.
- Added explicit <filesystem> includes where types were previously
available only transitively (db.h, rpcblockchain.cpp).
boost::thread -> std::thread (12 files):
- sync.h CCriticalSection/CWaitableCriticalSection now alias
std::recursive_mutex/std::mutex. boost::unique_lock and
boost::condition_variable / boost::mutex::scoped_lock swapped to std
equivalents; sync.cpp boost::thread_specific_ptr -> thread_local
std::unique_ptr.
- init.cpp boost::thread_group rewritten as std::vector<std::thread> with
manual join loop. boost::thread::hardware_concurrency ->
std::thread::hardware_concurrency.
- main.cpp/wallet.cpp -blocknotify/-walletnotify shell-out threads now use
std::thread(...).detach() — fixes a latent bug where modern boost::thread
destructor would call std::terminate on the joinable thread.
- util.cpp NewThread now catches std::system_error.
- No interruption_point/interrupt usage anywhere — pure mechanical swap.
boost::chrono / boost::posix_time -> std::chrono (3 of 5 files):
- util.h: MilliSleep, GetTimeMillis, GetTimeMicros rewritten on std::chrono
(system_clock for epoch math, sleep_for for delays).
- snapshotnet.cpp: sleep_for swapped.
- DoS_tests.cpp: timing harness uses steady_clock.
- Skipped: rpcdump.cpp (boost::posix_time::time_input_facet has no clean
std::get_time equivalent) and qt/qtipcserver.cpp (locked to
boost::posix_time by boost::interprocess::message_queue::timed_receive).
Other housekeeping:
- Dropped unnecessary "using namespace boost;" from txdb-leveldb.cpp,
txdb-rocksdb.cpp, walletdb.cpp, db.cpp (verified no unqualified boost
names in those TUs).
- Removed unused extern declaration for clearwallettransactions.
Build fixes for non-unity builds on MinGW64/GCC 15:
- net.cpp: dropped stale #include "irc.h".
- addrman.cpp + main.cpp: explicit <cmath> include for sqrt/pow (was
arriving transitively via boost headers).
- rpcblockchain.cpp + init.cpp: defensive #undef STRICT/ADVISORY/PERMISSIVE
since windows.h macros collide with the Checkpoints:: enum values when
std headers reorder include flow.
- tor_embed_hooks.cpp: triangles_tor_check_interrupted now polls fShutdown
instead of boost::this_thread::interruption_requested (we never used
boost interruption — the hook was always effectively a no-op).
- snapshotnet.cpp: fs::remove error handle uses std::error_code.
- serialize.h: added <ios> for std::ios::badbit/failbit (was relying on
transitive include via boost).
Note: unity builds currently fail on this branch due to std::byte (C++17)
colliding with COM 'byte' typedef from shlobj.h when 'using namespace std;'
from earlier files in the unity slice leaks into util.cpp's parse of
shlobj.h. Build with -DENABLE_UNITY_BUILD=OFF (the default).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The >24h block-time check in IsInitialBlockDownload() incorrectly kept
IBD=true when the chain was fully synced but simply had no new blocks
arriving (stalled network). This prevented the stake miner from ever
proceeding past its IsInitialBlockDownload() wait loop.
Now returns false once we've passed the checkpoint height estimate,
which correctly indicates IBD is complete.
Fixes: stake miner stuck even when chain is fully synced
Allow historical chain sync to bypass the mandatory coinbase-height
check. Triangles blocks from the original chain do not encode block
height in the coinbase scriptSig, so unconditional enforcement causes
AcceptBlock to reject valid historical blocks during IBD.
Activation set to 2,300,000 — past the original chain's maximum height
but before any future activation point.
M1.2 (mechanical):
Convert every CTxDB& parameter and reference across main.{h,cpp},
wallet.{h,cpp}, and smessage.cpp to CTxDBBase&. Local instantiations
like `CTxDB txdb("r");` are deliberately left as concrete LevelDB —
they'll move behind a factory in M1.4 once the parity harness exists.
CTxDB IS-A CTxDBBase, so all existing call sites continue to compile:
a CTxDB instance binds to a CTxDBBase& parameter automatically.
Forward declaration `class CTxDB;` in main.h replaced with
`class CTxDBBase;`.
Parallel work (snapshotnet + version bump to 5.9.4 + checkpoints/init
/protocol/version edits) included so origin/master matches the local
working tree in one push.
NOT YET COMPILE-TESTED: pushed at the user's explicit request before
the build verification step. If CI fails, expected breakage is in
files that include main.h transitively but not txdb-base.h — fix is
to add `#include "txdb-base.h"` (or rely on the existing txdb.h which
pulls it in).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>