feat(wallets): exact address/tx counts via a minimal BDB btree parser

parseWalletBtree() walks the wallet.dat Berkeley DB btree directly (no libdb, no
daemon): validate the metapage magic, follow the "main" sub-database (its pgno is
stored big-endian in the master map), traverse internal/leaf pages, and tally
records by their length-prefixed type name — transparent + shielded spendable
keys, address-book, and tx count, plus exact encryption/seed flags. Every offset
is bounds-checked, pages are deduped at push time (stack stays O(npages)), and a
visited-set + page/key caps make it safe on a corrupt/adversarial imported file;
files using DB_CHKSUM/DB_ENCRYPT (which shift the page layout) are rejected so the
byte-scan fallback runs instead.

The async probe now uses this as the primary path (exact badges + counts), falling
back to the byte-scan only when the btree can't be fully parsed. The wallets list
shows "N keys · M txs" for probed rows (labeled "keys", not "addresses", since the
count includes change keys the daemon's address list omits). Validated read-only
against real wallets (counts cross-checked; enc/seed match the byte-scan) and a
hand-built minimal btree fixture in the unit suite.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-11 13:20:35 -05:00
parent 36efe99a0f
commit 175950a2ec
4 changed files with 229 additions and 8 deletions

View File

@@ -258,6 +258,8 @@ void I18n::loadBuiltinEnglish()
strings_["wallets_col_name"] = "Wallet";
strings_["wallets_col_size"] = "Size";
strings_["wallets_col_addresses"] = "Addresses";
strings_["wallets_col_txs"] = "txs";
strings_["wallets_col_keys"] = "keys";
strings_["wallets_col_balance"] = "Balance";
strings_["wallets_col_opened"] = "Last opened";
strings_["wallets_current"] = "current";