- Checkpoints_tests: align with the checkpoint map refreshed 2026-07-01
(2186940 pin superseded by 2205000/2206004 pins).
- wallet_tests: make abandon_not_from_me self-sufficient; add_coin() never
populated mapWallet, so the test provisions its own not-from-me tx.
- DoS_tests: RFC 6979 deterministic-signing fix (from prior audit session).
- http_seed_tests: correct chunked-body byte math in
dechunk_split_at_awkward_boundary (\r\r\n is 3 bytes, not 2).
- onion_v3_tests: .onion.onion fix (from prior audit session).
- time_drift_tests: post-fork drift limit is 90s (main.h), not 180s.
- consensus_safety_tests: new suite pinning consensus constants
(MAX_REORG_DEPTH, MAX_MONEY, fork heights, fee floors, etc.).
- CMakeLists: TEST_DATA_DIR definition quoting fix.
The hardened PowerShell retry loop from 2c2efd8 passed -ConnectionTimeout
and -OperationTimeout to Invoke-WebRequest. Those are PowerShell 7+ only;
GitHub Actions Windows runners ship PowerShell 5.1, which rejected them
with 'ParentContainsErrorRecordException / NamedParameterNotFound' on
the first iteration of the loop, and the catch block silently counted
the syntax error as a 'failed attempt' instead of a script bug.
Result on run #28689210122: both Windows jobs (build-windows-qt,
build-windows-daemon) failed at 'Download Tor' / 'Bundle Tor for daemon'
with exit code 1 before any HTTP traffic happened. macOS + Linux passed.
Fix:
* Drop -ConnectionTimeout and -OperationTimeout (PS7-only).
* Restructure the retry loop: explicit $downloaded flag, remove the
part-file on each attempt, throw explicitly at the end if all 3
attempts produced no usable file. The size check (>1MB) still
rejects 0-byte / truncated '200 OK' responses.
* Add a comment at the top of each step explaining the PS 5.1 limitation
so the next agent doesn't re-add the PS7 params.
The macOS build of e2cd0b6 (the NeedsBootstrap rocksdb/ fix) failed at
the 'Bundle Tor into app' step with bash exit code 6 after exactly 30s
of curl hanging against archive.torproject.org. All 4 Tor download
sites (Windows Qt, Windows daemon, Linux Qt .deb, Linux daemon .deb,
macOS Qt) used 'curl -sL' with no timeouts and no retries — a single
transient network drop from Azure westus to the Tor archive killed
the job.
Fix at all 4 sites:
* curl -fSL (HTTP error -> non-zero exit; fail loudly)
* --connect-timeout 15 / --max-time 120 (per-attempt bounds)
* --retry 3 --retry-delay 5 --retry-connrefused --retry-all-errors
(covers 5xx, DNS timeouts, and connection refused)
* 'set -euo pipefail' at script top so any failure aborts cleanly
* PowerShell variants get a manual retry loop with size check
(1MB minimum — a 0-byte '200 OK' response from a broken mirror
used to silently slip through)
Also bump CLIENT_VERSION_REVISION 1 -> 4 (v6.1.4) for the upcoming
release that will include e2cd0b6 (NeedsBootstrap rocksdb/ fix).
Release notes:
v6.1.4: Tor bundle download resilience (4 CI sites hardened)
+ e2cd0b6 (NeedsBootstrap rocksdb/ chain state detection). Supersedes
v6.1.3 only on CI reliability; no protocol/wallet/chain format changes.
The chain DB detection at src/bootstrap.cpp:51-61 checked for txleveldb/,
blocks/chainstate/, and chainstate/ — but not rocksdb/. After the LevelDB
to RocksDB migration completes on v6.1.x, the live chain state lives in
rocksdb/. If the legacy txleveldb/ directory is removed (a reasonable
cleanup operation now that the migration is done), the boot path
incorrectly decides 'no blockchain data found' and triggers a 943 MB
bootstrap download over Tor. DNS2 incident 2026-07-03: 5-hour wedge from
exactly this; recovery via v3 snapshot drop + rm -rf rocksdb + restart.
Add fs::exists(dataDir / "rocksdb") to the OR-chain so a fully-migrated
node stays recognized as 'has chain DB' even after txleveldb/ cleanup.
The four states this handles correctly:
- Fresh node (no chain DB): bootstrap → snapshot → load
- Mid-migration (txleveldb + no rocksdb): don't bootstrap, migrate
- Post-migration (both): don't bootstrap, load RocksDB
- Post-cleanup (rocksdb only, the broken case before this fix): now
correctly recognized as 'has chain DB' — don't bootstrap, load RocksDB
Ref: references/needsbootstrap-rocksdb-gap-2026-07-03.md (full incident
notes, recovery recipe, defense-in-depth notes on the auto-snapshot
loader at init.cpp:1260 which is already backend-aware).
The cherry-pick of updateHDStatus/updateI2PAddress from master left the
TrianglesGUI ctor without the corresponding label_hd / label_i2p /
label_i2p_icon / label_tor_icon wiring, and trianglesgui.h missing the
function declarations. Master compiles because all four exist together.
Add the constructor blocks guarded by findChild so they no-op on
hd-on-master's narrower UI (these widgets aren't added yet) and just-
work when master merges in the I2P-UI work. Add the missing function
declarations to the header.
The clang-format-diff and clang-tidy-diff jobs were hard-coded to
origin/${{ github.base_ref }}, which is empty under workflow_dispatch.
When the workflow was triggered manually (no PR context), both jobs
failed with 'Not a valid object name origin/' before doing any work.
Fallback path: when base_ref is empty, run clang-format/ clang-tidy
against initial commit..HEAD (i.e. the whole repo) so a manual dispatch
still produces a useful signal. Saves the diff to /tmp/changes.diff and
skips clang-tidy entirely if the diff turns out empty.
Adds OutlinedLabel, a small QLabel subclass that paints each character
with a colored outline and a hollow interior. Used for the HD badge
in the status bar so each letter H and D is bordered in the same
- outline + fill done in custom paintEvent (no QSS hacks)
- updateHDStatus() now drives setOutlineColor/setOutlineWidth
directly instead of stylesheets
- registered OutlinedLabel as a custom widget in mainwindow.ui
- labelHdIcon pointer type updated to OutlinedLabel*
v6.1.3 distribute run (#28579791121) failed all 4 jobs (Homebrew, AUR,
Docker Hub, WinGet) because the build workflow took >12 minutes to
publish the GitHub release with binary assets, but the distribute
workflows only waited 10 minutes (30 iterations x 20s).
The race:
- Build workflow runs in parallel with Distribute workflow (no `needs:`)
- Distribute polls for the .deb/.dmg/.exe assets at the release URL
- Old 10-minute hard timeout was tuned for ~5 minute builds
- Modern builds (Windows, sanitizers, full Qt) routinely take 20-30 min
Bump all 5 wait loops (Docker Hub, AUR, Homebrew, Chocolatey, WinGet)
from 30 to 90 iterations, total 30 minutes, and update the error
messages to reflect the new timeout. Error messages also gained the
"after 30 minutes" suffix for consistency.
No change to the trigger conditions or job logic — only the timeout.
This is a workflow-only change; no source or CI matrix changes.
The chaindb_wipe test suite runs after chaindb_backend_selection and
rocksdb_wrapper, both of which leave the process-wide static g_rocksdb
(and on some paths the leveldb txdb singleton) alive. A leaked
g_rocksdb means the next test that does MakeChainDB('cr+') may get a
path that the prior test's open handle is still serving — leading to
the test operating on stale state and the on-disk wipe having no
effect. The H1 crashed_migration_marker_triggers_retry test
specifically could not bootstrap a fresh txleveldb/ for the migration
because the leveldb handle from the prior test was still bound.
This is the same class of bug as W2 (live LevelDB iterator outliving
the DB close) but at the test binary's process-lifetime scale: a live
DB handle from a prior test leaks into the next test and the on-disk
wipe is a no-op.
Fix: add a ResetChainDBStatics() helper that explicitly opens + closes
both backends (in create-if-missing mode so it works whether or not a
prior test left a DB on disk) and then wipes the on-disk chain DB
directories. Call it at the top of every chaindb_wipe test.
Before: 2/4 chaindb_wipe tests passing (H1 retry, H4 happy path) due
to the static-state leak. The crashes were also producing spurious
SIGABRTs at process exit from the static VersionSet assertion.
After: 20/20 chaindb_runtime tests pass, 3/3 chaindb_equivalence,
14/14 snapshotnet.
One file, +52 lines, no production code changes.
Three fixes for the chain-DB migration path on real chain data.
All three were uncovered when running the full DNS2 2.2M-block chain
end-to-end; the existing 18 unit tests passed because they exercised
small fixtures, never the real migration entry point.
W2 (root cause): chaindb_migrate.cpp — scope the source.NewIterator()
inside an inner block so it's destroyed BEFORE source.Close(). Live
LevelDB iterators hold a Version ref; closing the DB with one alive
trips the dummy_versions_.next_ == &dummy_versions_ assertion in
leveldb::VersionSet::~VersionSet (version_set.cc:755), aborting the
daemon after verification but before the marker is removed. This
explains the original H4 symptom: the daemon died in the gap between
'verified' and 'fs::remove', and Release builds hid it by compiling
asserts out. The H1 retry path's static-state issue in the test
binary is the same bug at process exit. In-loop failures now break
out with fCopyOK=false and are handled after the iterator dies.
H4 (defense in depth): chaindb_migrate.cpp — keep the verify-and-fail
hardening even though W2 fixes the cause. Use the non-throwing
error_code overload, fs::exists verify after remove, single 100ms
retry (Windows AV/indexer transient locks), hard-fail strError if
the marker still survives. Operator-visible failure beats silent
re-migration time bomb. The H4 invariant: a successful migration
never leaves the marker on disk.
W1: init.cpp — Lookup('0.0.0.0', addrBind, GetListenPort(), false)
replaced with direct CService construction from in_addr{htonl(INADDR_ANY)}.
This was the bug that prevented fc7ad5b from ever starting on
SAMI-PC; Windows getaddrinfo doesn't always map the literal '0.0.0.0'
string to INADDR_ANY.
Test: chaindb_runtime_tests.cpp — adds marker_removed_after_successful_migration
which exercises the real MaybeMigrateLevelDbToRocksDb() end-to-end on
the happy path. Complements the existing
crashed_migration_marker_triggers_retry (retry path). This is the
gap that hid the original bug: no test went through the production
entry point on the happy path.
Runtime verification: full DNS2 chain state (txleveldb 1.1GB +
blk0001.dat 942MB, 6.77M records) migrated end-to-end. MIGRATION_INCOMPLETE
absent from disk after. Reopened rocksdb reads back cleanly via
getblockcount / LoadBlockIndex.
Three files, 152 insertions, 27 deletions, build clean, CI ready.
Add a [HD] label next to the lock icon that shows whether the
wallet has a BIP39 HD seed active:
- Red (#f26522, TRI brand color) when HD is enabled
- Grey (#555555) when wallet is non-HD (legacy key pool)
Tooltip on hover explains what HD means and what the user must
back up to be able to restore the wallet.
Wired through a new updateHDStatus() slot that reads the
public WalletModel::hdEnabled() accessor and is called when
the wallet model is set. Lives in the icon cluster of the
status bar; the .onion and .b32.i2p address text sits in a
separate group on the right, so no crowding.
Closes the visible-state gap: the wallet already supported
HD seeds (BIP39/BIP32) and had a hdseeddialog, but there was
no visual confirmation of HD status anywhere in the UI.
Adds a Boost test that pre-creates a rocksdb/ dir with a
MIGRATION_INCOMPLETE marker and verifies the init path:
1. Detects the marker
2. Refuses to open the rocksdb/ dir as live state
3. Re-runs the migration
Also exercises the M4 marker flush+verify path: marker is on
disk only during an in-progress migration and removed on success.
This test was in the H1/H2/H3/M4 patch but never committed;
folding it in here so the test surface matches the audit doc.
H1: init.cpp now detects crashed migrations (MIGRATION_INCOMPLETE marker)
and retries instead of opening a partial RocksDB. Refuses to start if
the marker persists after migration attempt.
H2: LevelDB ExistsRaw now returns false for keys deleted in the active
batch, matching ReadRaw and the RocksDB backend. Fixes latent
cross-backend consensus split in intra-batch spend checks.
H3: All RocksDB close paths now go through close_rocksdb() which
destroys CF handles before deleting the DB. Fixes RocksDB assertion
/ UB on shutdown and version-reset.
M4: Migration marker write is now flushed + verified (refuses to start
migration if marker can't be written).
CF routing permanently disabled: GetCF() always returns nullptr (default
column family). The read path (NewIterator, LoadBlockIndex) only iterates
the default CF, so writes routed to per-prefix CFs were invisible to scans.
This is why -chaindb=rocksdb compiled clean but was never runtime-valid.
Existing CF-enabled DBs still open (handles retained for cleanup) but no
routing occurs. CF-aware iteration is a future follow-up.
txdb-factory: RocksDB is now the default backend (was still leveldb).
Tests updated for RocksDB-as-default expectations.
From Claude's ROCKSDB-T010-REVIEW-2026-07-01 audit on E:\repos\triangles.
hdPassphrase was hardcoded to empty string in DeriveHDKey, meaning users
who set a BIP39 passphrase during seed creation would derive different
addresses after restoration. This adds proper passphrase storage,
encryption, and decryption alongside the existing mnemonic handling:
- wallet.h: hdPassphrase + vchCryptedHDPassphrase + hdPassphraseIV fields
- wallet.cpp: Lock/Unlock/EncryptWallet/SetHDSeed all handle passphrase
with the same encrypt/decrypt lifecycle as the mnemonic
- DeriveHDKey now passes hdPassphrase to DeriveTriangles (not hardcoded )
- walletdb.h: WriteHDPassphrase/WriteHDCryptedPassphrase/EraseHDPassphrase
- walletdb.cpp: ReadKeyValue handles hdpassphrase/hdcpassphrase records
- rpcwallet.cpp: hdnew/hdshow show passphrase_used + warnings
Also: i2p.cpp hardens I2P private key file permissions to owner-only.
From Claude's uncommitted work on E:\repos\triangles (SAMI-PC). The rest
of Claude's modernization (Boost removal, RPC rewrite, RocksDB default,
SQLite wallet) was already committed to master in bfdb399 and follow-ups.
A v3 snapshot carries the UTXO set and raw block data (blk0001.dat) but
does NOT rebuild the per-tx index (txindex) that maps CTransaction hashes
to CDiskTxPos. Without it, any new PoS block referencing a pre-snapshot
transaction fails CheckProofOfStake with 'read txPrev failed':
CTransaction::ReadFromDisk(txdb, prevout, txindex) // src/main.cpp:714
if (!txdb.ReadTxIndex(prevout.hash, txindexRet)) // empty!
return false;
This stalls the node at the snapshot height and triggers DoS=100 on
every inbound peer feeding canonical blocks, masking as a network
misbehavior issue. DNS3 was stuck at 2,214,547 for this reason despite
the UTXO and block data being present.
Fix: after extracting blk0001.dat, walk it linearly and call
txdb.UpdateTxIndex(hash, CTxIndex(CDiskTxPos, nVout)) for every
transaction. O(N) over the historical block range, batched every
5000 txs. Adds ~30-60s to snapshot load on modern hardware.
This is the third leg of the v3 self-contained snapshot story:
v3 field source purpose
----------- ------------------------- -------------------------
headers last 2000 block headers block index continuity
utxos 22k unspent outputs UTXO set at tip
blocks blk0001.dat raw bytes on-disk block storage
setStakeSeen last 5000 PoS seen stakes stake collision dedup
txindex [this commit] PoS signature verification
Future 'v4 snapshot' work should consolidate all five into a single
load pass with progress reporting.
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).
The writer seek calculation (contentHashPos - sizeof(numUtxos)) was
correct for v1/v2. In v3 the layout inserted numBlocks between
numUtxos and numStakeSeen, so the seek landed on the numBlocks field
and the updated count was written there, corrupting both fields.
Fix: compute the offset relative to contentHashPos, skipping the
contentHash, numStakeSeen, and numBlocks fields inserted in v2/v3.
Adds 8 new hardened checkpoints at heights 2206500-2214400, verified
against the canonical chain. Closes the 8,400-block unverified gap
between the last hardcoded checkpoint (2206004) and the live tip
(2,214,476).
Without these, a fresh node syncing from zero with NO snapshot has
zero finality protection above height 2206004. A peer feeding fork
blocks at heights 2206005-2214400 could trick the IBD node into
accepting a divergent chain, because CheckHardened() only fires at
the exact heights in mapCheckpoints.
With continuous pins every 1000 blocks, any divergence >1000 blocks
is rejected at AcceptBlock time with DoS=100, protecting from-zero
sync against low-trust forks.
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.
GCC 11+ on Intel CI runners (Skylake-X, Ice Lake, Sapphire Rapids)
emits AVX-512/AVX10 instructions for std::string / memcpy inlining
that crash with SIGILL on AMD EPYC and older Intel without those
extensions. Root cause: libstdc++ is statically linked into the
binary, so the build host's instruction set becomes a hard runtime
requirement.
The CI binary crashed immediately on DNS2/DNS3 (AMD EPYC Milan) with:
traps: trianglesd[...] trap invalid opcode ip:...e432 error:0
in trianglesd[...+af3000]
Disassembly of the crash site (file offset 0x15b432):
62 f1 7f 08 6f 41 ff vmovdqu8 -0x10(%rcx), %xmm0
This is an AVX10/AVX-512 instruction emitted inside
std::basic_string::basic_string (statically linked libstdc++).
Fix: -march=x86-64-v2 -mtune=generic for all Linux x86_64 builds.
v2 baseline (SSE4.2 + POPCNT + CMPXCHG16B) is from 2009 Nehalem and
supported on every x86_64 CPU we ship to. Override-able via
-DCMAKE_X86_64_BASELINE=OFF if a CPU-specific build is needed.
Adds v3 snapshot format that includes the last 5000 PoS block
(prevoutStake, nStakeTime) pairs so a snapshot-loaded node has its
stake-collision set restored without walking the block index.
v2 readers still load v3 snapshots (the extra field is past numBlocks
and the loader checks version >= 3 to read numStakeSeen).
Bumps version to 6.1.1.
SQLite wallet (default), RocksDB chaindb (default), Boost removal
from common link, I2P startup performance fix (180s→27ms).
Migration is automatic and non-destructive:
- Wallet: BDB → SQLite on first open (original preserved as .bdb.bak)
- ChainDB: LevelDB → RocksDB on next start (via -chaindb flag)
cpp20 modernization made SQLite the default wallet backend
(find_package(SQLite3 REQUIRED)). The windows-qt job got it
transitively via Qt5-base, but the daemon job had no such
dependency → CMake configure failure.
The cpp20 rebase removed all Boost includes from init.cpp but left
this orphaned using-directive. It compiled on Linux daemon (I2P
embedded transitively provides the boost namespace) but broke every
Qt/Windows/macOS build in CI with: 'boost' is not a namespace-name.
i2p::api::StartI2P() calls NetDb::Start() which triggers Reseed() — a
blocking HTTPS download from public I2P reseed servers. On first run
(empty netDb), this blocks for up to 180s, freezing the GUI splash
screen on "Starting embedded I2P router..." indefinitely.
Fix: Move StartI2P + client::context.Start + SOCKS/SAM readiness polling
into a detached background thread. InitI2P (fast, <1s) remains
synchronous. The main init thread proceeds immediately — Tor-only mode
works while I2P bootstraps asynchronously (1-5 min for first run).
Verified: i2p_start startup perf dropped from 180s+ to 27ms on first run
with empty netDb. Reseed + tunnel building continues in background.
Live-validation of the CWalletDB rebase (preserved snapshot wallet from
DNS2's 2026-04-23 recovery, copied to /tmp/cpp20-validate, run with the
new build) revealed that after MaybeMigrateBerkeleyWalletToSQLite()
converts wallet.dat from Berkeley DB to SQLite, the next block of init
code still calls bitdb.Verify() on the now-SQLite file. BDB cannot open
a SQLite file, so Verify returns RECOVER_FAIL, which init treats as a
fatal InitError ("wallet.dat corrupt, salvage failed"). The migration
itself succeeds and produces a valid SQLite wallet.dat + a
wallet.dat.bdb.bak copy of the original, but the daemon refuses to
continue past Step 5.
Fix: snapshot IsSQLiteFile(walletPath) AFTER the migration hook, then
gate the entire BDB-only Step 5 path (bitdb.Open, salvagewallet,
zapwallettxes, bitdb.Verify) on the snapshot. Once the wallet is SQLite
— whether by migration or because it was born that way — the BDB
verify/salvage steps are skipped; SQLiteDatabase::Open() already runs
PRAGMA integrity_check during connection setup, so the wallet is
validated against the SQLite schema before any handle is constructed.
The verify_db performance log now includes wallet_is_sqlite=N for
observability.
Validation: full migration cycle completed in the test datadir, RPC
getwalletinfo/getaddressesbyaccount returned valid data matching the
pre-migration BDB wallet exactly (5 unique HD-derived addresses,
keypoolsize=101, keypoololdest=1781772890, walletversion=60000).
Move CWalletDB off the Berkeley CDB base class and onto the typed batch
seam introduced by walletdb-batch.h / walletdb-{factory,sqlite}.{h,cpp}.
Build seam
----------
* CWalletDB now derives from CWalletBatchTyped. The typed Read/Write/
Erase/Exists templates come from the seam; their bodies (WriteTx,
WriteKey, WriteMasterKey, ReadPool, WriteSetting, ...) are unchanged
because only the base class swapped — the call signatures resolve to
the same templates.
* CWalletBatchTyped takes ownership of the WalletDatabase so the
underlying connection outlives any batch issued by it (SQLiteBatch
holds a reference, not a value). The two-phase Open() pattern lets
CWalletDB hand the freshly opened database to the base class after
MakeWalletDatabase() returns.
* MakeWalletDatabase (walletdb-factory.cpp) routes -walletdb=sqlite to
SQLiteDatabase, returning nullptr with a clear error for the
unfinished Berkeley branch. The CWalletDB constructor surfaces that
error string on failure.
Cursor sites (the only Berkeley-specific call sites)
---------------------------------------------------
Three sites used GetCursor()/ReadAtCursor() directly:
* LoadWallet — full scan, now uses StartCursor()/NextRecord()
* ListAccountCreditDebit — used DB_SET_RANGE + DB_NEXT loop; replaced
with full keyspace scan + filter-in-loop (SQLite cursor does not
support keyed range seeks). Behaviour matches Berkeley: terminates
when strType changes or, in single-account mode, when
acentry.strAccount differs.
* ZapWalletTx — moved to BerkeleyZapWalletTx (see below) because it
operates on raw Berkeley Db/Dbc/Dbt now that CWalletDB is on the
seam.
The 3 unused public methods on the old CWalletDB (GetAtCursor /
GetTxnCursor / GetAtActiveTxn) had no callers outside walletdb.{h,cpp}
(verified by grep) and were removed.
Berkeley-only escape hatches
----------------------------
Recover(CDBEnv&,...) and ZapWalletTx(...) became BerkeleyRecoverWallet
and BerkeleyZapWalletTx in a new walletdb-recover.{h,cpp} pair. They
operate directly on DbEnv/Db/Dbc/Dbt because CDB's members are
protected (free functions cannot use the wrapper). The recovery logic
duplicates a BDB-only ReadKeyValue variant locally to avoid pulling
the typed batch seam into a Berkeley-only file.
Init.cpp uses these via:
* -salvagewallet -> BerkeleyRecoverWallet(bitdb, ..., fOnlyKeys=true)
* -zapwallettxes -> BerkeleyZapWalletTx(...)
* bitdb.Verify -> BerkeleyRecoverWallet as the recover callback
Wallet migration hook
---------------------
After the Berkeley verify/salvage/zap steps and before CWalletDB is
opened for the live wallet, init.cpp now calls:
if (ResolveWalletDbKind() == SQLite &&
!IsSQLiteFile(walletPath))
MaybeMigrateBerkeleyWalletToSQLite(walletPath, err)
The migration code (walletmigrate.{h,cpp}) is unchanged — it opens a
private Berkeley environment over the wallet directory, copies every
record verbatim (raw key/value bytes) into a fresh SQLite file,
verifies the row count, then atomically renames the BDB original to
"<name>.bdb.bak" and the SQLite file into place. On any failure the
BDB original is left exactly as it was. Errors surface through
InitError so the daemon refuses to start with a corrupt wallet rather
than silently falling back to Berkeley.
No working Berkeley fallback
----------------------------
MakeWalletDatabase returns nullptr for the Berkeley branch, so
-walletdb=bdb no longer opens a working wallet through the seam. This
is intentional for this release — the migration hook handles existing
BDB wallets at first startup, after which the on-disk file is SQLite
and the BDB code path becomes pure recovery glue.
Header fallout
--------------
walletdb.h no longer pulls in db.h (which would drag <db_cxx.h> into
every TU that includes the wallet API). Forward decls added for
CWalletTx, CBlockLocator, CWallet, CPubKey, CScript, CMasterKey,
uint160, uint256. nWalletDBUpdated is now extern-declared in
walletdb.h and defined in db.cpp (was previously declared in db.h).
Validation
----------
Build: GREEN with USE_TOR_EMBEDDED=ON USE_I2P_EMBEDDED=ON. 6 binaries:
trianglesd, triangles-cli, test_triangles, test_chaindb_runtime,
test_chaindb_equivalence, test_snapshotnet.
Tests: 107/107 + 10/10 + 5/5 = byte-identical to the 5d9da84
baseline. wallet_tests and accounting_tests inside test_triangles now
exercise the SQLite path for the first time — their pass is the
de-facto wallet-migration validation at the test-suite level.
Modernizes triangles.conf parsing to use plain stdlib instead of
boost::program_options::detail::config_file_iterator.
Supports the syntax that actual triangles.conf files use:
- key=value or key = value (whitespace around = is ignored)
- # comments and blank lines are skipped
- Surrounding double quotes are stripped from values
- Same InterpretNegativeSetting semantics
- Command-line settings still take precedence (don't overwrite existing)
Intentionally NOT supported (different from Boost):
- Backslash line continuations
- Escape sequences inside quoted values
- Section headers ([section])
After this change, triangles_common no longer uses Boost.ProgramOptions
directly. The Boost.ProgramOptions link inside the USE_I2P_EMBEDDED block
remains because i2pd-src/libi2pd/Config.cpp uses it internally.
Verified:
- Build with -DUSE_I2P_EMBEDDED=ON succeeds
- Build with -DUSE_I2P_EMBEDDED=OFF succeeds
- Without i2p, ldd on trianglesd shows NO libboost_program_options dep
Merge left StopI2P() (duplicated StopEmbeddedI2P), StartI2P(),
CI2PProcess::GetInstance(), and I2P_DEFAULT_SAM_PORT references from
the SAMI-PC process-based I2P. Replaced with the v6 embedded I2P API:
- shutdown: single StopEmbeddedI2P() (was called twice plus StopI2P)
- startup: single StartEmbeddedI2P() which reads its own args
- removed manual SAM host/port resolution (StartEmbeddedI2P handles it)
Merge left residual references to the SAMI-PC process-based I2P API
(CI2PSession, fI2P) in files that now compile against the v6 embedded
I2P (CI2PEmbedded). Fixed:
- net.cpp ConnectNode: removed fI2P/CI2PSession blocks, restored
v6 SOCKS-proxy connection path (I2P routing handled in netbase)
- CMakeLists.txt: removed i2p.cpp/i2p_process.cpp from build (not
part of embedded I2P; kept in tree as reference only)
- rpcnet.cpp: CI2PSession → CI2PEmbedded (IsRunning/GetI2PAddress)
- rpcwallet.cpp: same API migration
- init.cpp: same API migration for startup address print
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.
i2pHostname was cleared on Start() but never populated, so
GetI2PAddress() always returned empty and the Qt status bar never
showed the I2P address even when the router was running.
Now queries i2p::context.GetRouterInfo().GetIdentHash().ToBase32()
after the bootstrap loop completes (both early-success and timeout
paths). The address appears as <hash>.b32.i2p in the status bar.
libboost_system-mt.a doesn't exist on MSYS2 (header-only in newer
Boost). find_library auto-discovers the actual filenames and skips
any that don't exist. Resolves both the missing-file error and the
naming ambiguity.
MSYS2 MinGW doesn't create CMake imported targets for Boost, so
Boost::filesystem etc. silently don't link. i2pd's own Makefile.mingw
solves this by referencing full paths like /mingw64/lib/libboost_*.a.
We do the same — auto-detect MINGW_PREFIX (/mingw64) and link the
exact .a files for boost_filesystem, boost_program_options,
boost_system, openssl, and zlib.
The linker needs to see i2pd archives, then Boost/zlib to resolve
their symbols, then i2pd archives AGAIN to resolve any remaining
references. Added raw -l fallbacks for MinGW where Boost:: CMake
imported targets may not exist even though libs are installed.
Windows: Replace --start-group/--end-group (CMake mis-orders them
with Ninja generator) with double-listing of i2pd static archives.
Linker resolves circular deps in two left-to-right passes.
macOS: Boost 1.90 via Homebrew doesn't provide filesystem/system as
separate COMPONENTS. Use OPTIONAL_COMPONENTS so find_package doesn't
fail, then guard the target_link_libraries with if(TARGET Boost::...).
The Boost::filesystem and Boost::system targets don't exist unless
find_package(Boost COMPONENTS ...) explicitly lists them. I2P's link
section references them but they were never found, breaking all
platforms.
libi2pd.a references boost::filesystem::detail::status, exists,
create_directories, etc. These are in boost_filesystem, which the
I2P link section was missing. Added Boost::filesystem and
Boost::system.