feat(wallets): show created date + sort the wallet list

Parse the earliest keymeta nCreateTime out of the wallet.dat btree (the daemon's
"wallet birthday") and surface it in each row's metadata line ("created Aug 2025").

Add a sort control above the list — Date created / Address count / Transaction
count / Wallet size, with an ascending/descending toggle (descending default:
newest / most / largest first). Sorting reorders a display-index array
(s_order) rather than s_rows, so the async, index-aligned probe batch is
untouched; sort keys read a single frame-consistent snapshot of the probe
results. Address count prefers the authoritative cached index value, else the
btree key count.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-11 16:37:36 -05:00
parent 8eefab99ec
commit 54a14485a5
4 changed files with 105 additions and 13 deletions

View File

@@ -790,27 +790,39 @@ void testWalletFileProbe()
EXPECT_TRUE(p.isBerkeleyDB); EXPECT_TRUE(p.scanComplete); EXPECT_FALSE(p.encrypted); EXPECT_FALSE(p.hdSeed); }
// 7) Btree record counting (tier 2): hand-build a minimal single-database BDB btree — a 512-byte
// metapage (magic/pagesize/root) + one leaf page holding a "tx" record and a "key" record — and
// confirm parseWalletBtree walks it and tallies them. Also that a non-BDB file yields parsed=false.
// metapage (magic/pagesize/root) + one leaf page holding "tx", "key", and a "keymeta" record — and
// confirm parseWalletBtree walks it, tallies the counts, and reads the keymeta nCreateTime (wallet
// birthday). Also that a non-BDB file yields parsed=false.
{
std::string w(1024, '\0');
auto put16 = [&](std::size_t o, unsigned v) { w[o] = (char)(v & 0xff); w[o+1] = (char)((v >> 8) & 0xff); };
auto put32 = [&](std::size_t o, unsigned v) { for (int k=0;k<4;k++) w[o+k] = (char)((v >> (8*k)) & 0xff); };
put32(12, 0x00053162u); put32(20, 512); w[25] = (char)9; put32(88, 1); // page 0: metapage, root = page 1
const std::size_t P = 512; // page 1: a btree leaf
put16(P + 20, 4); w[P + 24] = (char)1; w[P + 25] = (char)5; // entries=4 (2 pairs), level 1, P_LBTREE
put16(P + 26, 500); put16(P + 28, 496); put16(P + 30, 488); put16(P + 32, 484); // index array → item offsets
put16(P + 20, 6); w[P + 24] = (char)1; w[P + 25] = (char)5; // entries=6 (3 pairs), level 1, P_LBTREE
// index array → (key,data) item offsets, laid out high→low without overlap:
put16(P + 26, 500); put16(P + 28, 496); // tx
put16(P + 30, 488); put16(P + 32, 484); // key
put16(P + 34, 472); put16(P + 36, 456); // keymeta
auto keyItem = [&](std::size_t off, const std::string& type) { // BKEYDATA holding CompactSize(len)+type
std::string kd; kd += (char)type.size(); kd += type;
put16(P + off, (unsigned)kd.size()); w[P + off + 2] = (char)1;
for (std::size_t k = 0; k < kd.size(); ++k) w[P + off + 3 + k] = kd[k];
};
auto dataItem = [&](std::size_t off) { put16(P + off, 1); w[P + off + 2] = (char)1; w[P + off + 3] = 0; };
keyItem(500, "tx"); dataItem(496); keyItem(488, "key"); dataItem(484);
keyItem(500, "tx"); dataItem(496);
keyItem(488, "key"); dataItem(484);
keyItem(472, "keymeta");
// keymeta data = CKeyMetadata: nVersion(int32=10) + nCreateTime(int64) — little-endian.
const long long kCreated = 1700000000LL;
put16(P + 456, 12); w[P + 456 + 2] = (char)1; // BKEYDATA len=12, type=1, data@459
put32(456 + 3 + P, 10); // nVersion
for (int k = 0; k < 8; ++k) w[P + 456 + 3 + 4 + k] = (char)((kCreated >> (8 * k)) & 0xff);
writef(dir / "btree.dat", w);
auto s = dragonx::util::parseWalletBtree((dir / "btree.dat").string());
EXPECT_TRUE(s.parsed); EXPECT_TRUE(s.complete);
EXPECT_EQ(s.txCount, 1); EXPECT_EQ(s.transparentKeys, 1); EXPECT_EQ(s.addresses(), 1);
EXPECT_EQ(s.createdEpoch, kCreated);
EXPECT_FALSE(dragonx::util::parseWalletBtree((dir / "junk.dat").string()).parsed);
}