Commit Graph

642 Commits

Author SHA1 Message Date
Krystie 43dade4488 infra: reproducible build + signed release pipeline
Adds the infrastructure for verifiable Triangles releases:
- Reproducible builds (default-on): -ffile-prefix-map strips absolute
  source paths from binaries; SOURCE_DATE_EPOCH pinned to commit
  timestamp if env var not set. Two builds of the same commit with the
  same flags now produce byte-identical binaries.
- scripts/verify-reproducible-build.sh: builds the daemon twice into
  separate build dirs and compares SHA256. Pass/fail printed clearly.
- scripts/sign-release.sh: generates SHA256SUMS, writes detached .asc
  signatures over each release artifact and over SHA256SUMS itself.
  Supports --verify for independent third-party verification.
- release-process.md: canonical release pipeline documentation --
  reproducibility properties, signing-key setup, distribution
  requirements, failure-mode recovery, and the release checklist.
- scripts/README.md: updated to catalog the full scripts/ directory
  (was previously scoped only to bump-version.sh).

Verified end-to-end on this branch:
- scripts/verify-reproducible-build.sh: exit 0, both builds SHA256
  7a86d9659b7150f69dc53eb31cc4c7eb8df296b55fa889af5c5a1b310223c894.
- scripts/sign-release.sh: signs Release-built artifact, --verify
  returns exit 0 (all sigs + checksums valid).
- ctest: 4/4 suites still pass with the new compile flags.
- Tamper test: modifying an artifact after signing causes --verify
  to fail with '1 checksum(s) FAILED' (exit 1).

Existing signing key in the local keyring is used:
  523A81833EB7201573E1EFE1DCF2579968107984
  (Krystie Triangles Release <krystie-triangles-release@dns2.sami.tailnet>)

CI integration (separate PR): add a 'sign' job to build-all.yml that
imports GPG_PRIVATE_KEY from secrets and runs scripts/sign-release.sh
against the assembled release directory. Documented in release-process.md.
2026-07-07 01:43:17 -07:00
Krystie f50126a210 notes: 2026-07-06 session continuation -- keystore coverage shipped, PR #14 CI all real jobs green 2026-07-07 00:19:20 -07:00
Krystie f9a11fc3a2 notes: 2026-07-06 final session status -- PR #14 ready, kernel coverage shipped
Documents:
- V5 soft-cap test coverage shipped on audit/kernel-coverage (ab0f4b4)
- PR #14 CI status: test-linux-unit PASS, sanitizer FAIL pre-existing
  (simd.c:265 UBSan, separate workstream)
- Outstanding work prioritized for future sessions
- PR #14 is ready to merge
2026-07-06 23:18:14 -07:00
Krystie 8181216eb6 notes: 2026-07-06 session log -- DoS_checkSig timing fix on PR #14
Documents:
- Hermes's 2026-07-04 handoff letter had a stale 'blocked on W2' framing;
  W2/H4/W1 were already committed as 6cadf7f on 2026-07-02.
- This session's DoS_checkSig timing fix (commit b79e2b8): replaced the
  nonsensical nManyValidate < nOneValidate comparison with a stable
  per-verify bound (min of 3 trials after warmup, threshold 600ms
  calibrated to ~1.6x observed p100 on DNS2).
- PR #14 CI status: 9 jobs in progress as of session end.
2026-07-06 22:58:52 -07:00
Krystie b79e2b8215 test: replace DoS_checkSig cache-timing WARN with a stable per-verify bound
The previous timing assertion (nManyValidate < nOneValidate) was never
meaningful: the loops did different op counts (100 signs vs 500 verifies)
and the signature cache is intentionally a no-op on master, so cached-vs-
uncached verify cost is identical. The downgrade to BOOST_WARN_MESSAGE
that was on the branch fires every run.

Replace it with a real regression check: take the min of 3 timed batches
of 500 verifies after a warm-up pass, then assert the min is below an
empirically-calibrated threshold (600ms on this DNS2 dev box; real perf
~380ms in debug builds).

This catches genuine verify-path regressions (accidental O(n) cache key,
double-verify, hooking up OpenSSL instead of libsecp256k1) without
coupling to cache speedup that the on-chain code path explicitly avoids.

227/227 test cases pass, 21597/21597 assertions, 0 failures.
2026-07-06 22:56:24 -07:00
Krystie ded90736fc notes: crypter coverage added; remaining untested modules listed 2026-07-04 19:35:18 -07:00
Krystie 43eab5f8cd test: add wallet-encryption (CCrypter) coverage
crypter.cpp had zero tests despite guarding every encrypted wallet. Add
8 cases: passphrase round-trip for both KDFs (sha512 method 0 and scrypt
method 1), wrong-passphrase rejection, salt-affects-key, KDF determinism,
bad-parameter rejection (zero rounds / short salt / encrypt-before-key),
the EncryptSecret/DecryptSecret private-key path with a uint256 IV, and
ciphertext-tamper rejection. Round-trip/negative style, no brittle hard-coded
ciphertext. No implementation change (crypter.cpp is correct).

Note captured in the test: the wallet uses a uint256 as the AES IV but
AES-256-CBC consumes only the first 16 (little-endian) memory bytes -- a
subtlety worth remembering for anyone touching the key-encryption path.
2026-07-04 19:35:00 -07:00
Krystie bfe4681d97 notes: consensus sweep clean; CI ran zero tests (fixed); build hygiene 2026-07-04 16:26:10 -07:00
Krystie f0889d9b70 test/build: make ctest actually run the unit suites
Three coupled fixes to the test harness (no consensus/runtime code touched):

1. Root CMakeLists never called enable_testing(), so the top-level
   build/CTestTestfile.cmake was never generated and "cd build && ctest"
   (exactly what CI runs) discovered ZERO tests. The whole unit suite was
   silently not gating CI; only the explicitly-invoked equivalence binary
   ran. Add enable_testing() at the root so ctest finds all four test
   executables.

2. chaindb_runtime_tests.cpp and snapshotnet_tests.cpp were compiled BOTH
   into their own standalone executables AND into test_triangles via the
   test/*.cpp glob. Each #defines its own BOOST_TEST_MODULE and redefines
   the wallet/UI globals; the link only survived via
   -Wl,--allow-multiple-definition, which silently drops duplicate module
   and global symbols and can run those suites under the wrong fixture.
   Exclude both from the glob (they already have dedicated add_executable +
   add_test); nothing is lost and isolation is restored.

3. test_triangles TestingSetup opened the PRODUCTION chain DB at the default
   datadir, so ctest failed (DB lock) on any host running a live daemon and
   risked touching real chain state. Point -datadir at a fresh temp dir in
   the fixture (mirrors the standalone DataDirSetup); cleaned up on teardown.

After: ctest -N lists 4 tests; ctest runs 100% green even with a live
trianglesd holding the default datadir.
2026-07-04 16:25:26 -07:00
Krystie 16f3863e0c notes: chaindb/txdb audit -- no bugs, one equivalence-test coverage gap 2026-07-04 16:09:13 -07:00
Krystie 37a284160b notes: record no-consensus-change decision (reverted PoS + sigcache) 2026-07-04 15:15:45 -07:00
Krystie 30d9e9296d test: soften DoS_checkSig sig-cache timing to a WARN
With the signature-cache optimization intentionally left disabled (no
consensus-critical changes), cached and uncached verification cost the same,
so the nManyValidate < nOneValidate timing relation is not guaranteed. This
is a machine-dependent performance heuristic, not a correctness check, so
downgrade it from a hard CHECK to a WARN. CheckSig correctness is covered by
the multisig and script suites.
2026-07-04 15:15:12 -07:00
Krystie 36d5f2928f Revert "script: fix signature cache false positives (security)"
This reverts commit 239cf61795.
2026-07-04 15:10:07 -07:00
Krystie a78a420d76 test: tolerate 1-unit truncation rounding in PoS reward proportionality
After reverting the consensus-affecting PoS reward rework, the original
truncating formula (nCoinAge * rate / 365 / COIN) is restored. It is not
exactly proportional at every boundary (r2 can be 2*r1 +/- 1 due to integer
truncation). That rounding is the on-chain behavior and must not be changed
in consensus code, so relax pos_reward_proportional_to_coinage to allow a
1-unit difference rather than demanding exact doubling. Test-only change.
2026-07-04 15:08:21 -07:00
Krystie 05b56060ab Revert "main: PoS reward proportionality rework — NEEDS CONSENSUS REVIEW"
This reverts commit 2a4da3388f.
2026-07-04 15:03:51 -07:00
Krystie 6c209835b7 notes: record ReorderTransactions all-accounts fix + HD wallet coverage 2026-07-04 14:38:26 -07:00
Krystie b6b92602ed test: add HD wallet (BIP39/BIP32) coverage
The BIP39+BIP32 key derivation path (hdwallet.cpp) had zero tests despite
being security-critical and required to round-trip keys with the TRIdock
web wallet. Add canonical-vector tests:
- BIP39 Trezor english vector (mnemonic check + seed) and bad-checksum/
  bad-word/bad-length rejection.
- BIP32 spec test-vector 1 (master + m/0H hardened child), verified
  independently by base58-decoding the published xprv.
- DeriveTriangles determinism and index sensitivity.

No implementation changes: hdwallet.cpp derives correctly against the
canonical vectors.
2026-07-04 14:37:21 -07:00
Krystie b3720dbeb6 walletdb: reorder accounting entries across ALL accounts
ReorderTransactions called ListAccountCreditDebit("") which, after the
cursor-scan fix, returns only default-account entries. Entries booked to a
named account therefore kept nOrderPos == -1 forever and sorted incorrectly
in listtransactions. Use the "*" all-accounts sentinel, matching the
listtransactions RPC path and upstream Bitcoin.

Adds regression test acc_reorder_covers_named_accounts (fails on the old
code: named-account entry keeps nOrderPos == -1).
2026-07-04 14:37:21 -07:00
Krystie 2a4da3388f main: PoS reward proportionality rework — NEEDS CONSENSUS REVIEW
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.)
2026-07-04 14:18:59 -07:00
Krystie 239cf61795 script: fix signature cache false positives (security)
Two stacked bugs in CSignatureCache:

1. Set() keyed on vchSig (with trailing hashtype byte) while Get() keyed
   on vchSigCopy (without), so the cache never hit: a silent no-op.
   (Found in prior audit session.)

2. Once (1) was fixed, the cache produced FALSE POSITIVES: the 64-bit
   XOR-mixed key included the pubkey LENGTH but never the pubkey BYTES.
   All compressed pubkeys are 33 bytes, so a signature validated once
   hit the cache when re-checked against ANY other pubkey for the same
   sighash — CheckSig returned true without verifying. A 2-of-3
   CHECKMULTISIG could be satisfied by one valid signature duplicated.
   This also masqueraded as first-match-wins multisig reordering in
   multisig_tests/script_tests; those tests now pass with their original
   strict assertions.

Cache entries are now the full SHA256 over (sighash || sig || pubkey),
matching upstream Bitcoin Core; false positives are cryptographically
infeasible.
2026-07-04 14:18:58 -07:00
Krystie c2e05e1305 walletdb: fix SQLite cursor scan dropping accounting entries
ListAccountCreditDebit kept the Berkeley-era early-break on the first
non-acentry record. The BDB cursor was sorted and pre-seeked to the
(acentry, account) prefix via DB_SET_RANGE, so breaking was correct there.
The SQLite cursor (SELECT key, value FROM main) scans the whole keyspace
in unspecified order, so the loop usually hit the version record first
and returned zero entries: every wallet silently lost its accounting
history in the UI. Skip non-matching records instead of breaking.

Fixes all 27 accounting_tests/acc_orderupgrade failures.
2026-07-04 14:18:58 -07:00
Krystie 8d4d17e7a8 test: repair failing unit tests and add consensus safety checks
- 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.
2026-07-04 14:18:58 -07:00
Krystie 9aff1ea098 ci: fix Windows Tor bundle — drop PS7-only params from Invoke-WebRequest
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.
2026-07-03 18:50:10 -07:00
Krystie 2c2efd83fd ci: harden Tor expert bundle downloads against CI egress timeouts
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.
2026-07-03 17:25:16 -07:00
Hermes Agent e2cd0b6057 bootstrap: NeedsBootstrap check for rocksdb/ chain state
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).
2026-07-03 13:57:16 -07:00
SamiAhmed7777 bbc93c66a3 Merge pull request #12 from SamiAhmed7777/hd-on-master
HD wallet: outline HD status letters in TRI brand red (#f26522)
2026-07-03 01:02:07 -07:00
Krystie 8b7023810b qt(wallet): wire up HD/I2P/Tor status-bar icons
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.
2026-07-02 23:57:06 -07:00
Krystie e8e865557f ci(lint): don't fail on workflow_dispatch when base_ref is empty
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.
2026-07-02 23:55:56 -07:00
Krystie c7314b2357 qt(wallet): outline the HD status letters in TRI brand red
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*
2026-07-02 23:55:56 -07:00
Krystie 0712e5b08c ci(distribute): bump release-artifact wait from 10min to 30min
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.
2026-07-02 02:43:06 -07:00
Krystie 175abcd8a4 test: ResetChainDBStatics() helper to fix chaindb_wipe test isolation
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.
v6.1.3
2026-07-02 01:38:18 -07:00
Krystie 6cadf7f496 chaindb: W2 iterator-scoping + H4 marker-verify + W1 INADDR_ANY
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.
2026-07-02 01:29:03 -07:00
Krystie f9d1723f6e qt: HD wallet status indicator in status bar
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.
2026-07-01 21:32:58 -07:00
Krystie 35f524ff34 test: H1 crashed-migration marker retry test (M4 marker-write)
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.
2026-07-01 21:32:46 -07:00
Krystie fc7ad5bb69 rocksdb: apply T010 review fixes (H1/H2/H3/M4) + CF routing disabled
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.
2026-07-01 16:09:25 -07:00
Krystie a70019263d wallet: add BIP39 passphrase support throughout HD lifecycle
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.
2026-07-01 14:19:42 -07:00
Krystie ac0adfea15 snapshot loader: build txindex from blk0001.dat after extraction
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.
2026-07-01 13:29:28 -07:00
Krystie 9b5c47f60f anti-spam: revert comparison direction to > (cbb189a had it inverted)
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).
2026-07-01 12:42:59 -07:00
Krystie e48b71a5d1 ci: document manual dispatch command for TRI-PI rebuild without re-tag 2026-07-01 12:22:14 -07:00
Krystie d2389b4d39 sync: bump HEADER_DOWNLOAD_WINDOW 1024->4096 for 4x faster P2P IBD 2026-07-01 12:21:25 -07:00
Krystie 5c312bb7da snapshot v3: fix numUtxos update seek offset corrupting numBlocks
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.
v6.1.2
2026-07-01 11:25:31 -07:00
sami7777 41ba9f8bc9 checkpoints: continuous finality pins every 1000 blocks
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.
v6.1.1
2026-07-01 03:46:26 -07:00
sami7777 cbb189aade anti-spam: fix inverted condition + soft scoring
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.
2026-07-01 03:32:04 -07:00
sami7777 5635cb5e57 build: enforce -march=x86-64-v2 on Linux x86_64
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.
2026-07-01 03:26:31 -07:00
sami7777 b6feab8e94 snapshot v3: carry setStakeSeen over in dump/load
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.
2026-07-01 02:17:51 -07:00
Krystie 333f7abfc0 seed: add SAMI-PC I2P address as primary hardcoded seed
fecv4pomdm47epuadgrpkvxzjqfqwsjfc7t7xadwaac5bislyrhq.b32.i2p
2026-06-30 17:53:55 -07:00
Krystie eb20edf890 seed: add SAMI-PC as primary hardcoded onion seed
6ygpphp2qsucwvhwefv6h6ehvk6zjf7b7zdp4ggkzjjwe76cg6jwm7id.onion
is the authoritative wallet node — must be in every release.
2026-06-30 17:44:09 -07:00
Krystie ff7a7d3b6b build: bump version to 6.1.0
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)
v6.1.0
2026-06-30 15:54:45 -07:00
Krystie fb5f1be032 ci: add sqlite3 to Windows MSYS2 install (daemon+qt)
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.
2026-06-30 15:32:29 -07:00
Krystie 83a66814b0 fix: remove dead 'using namespace boost;' from init.cpp
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.
2026-06-30 15:12:00 -07:00