Files
triangles_v5/notes/w2-handoff-to-claude.md

5.6 KiB

Hey — pushing back on the H4 fix and adding a W2-equivalent crash on Linux that needs root-causing before v6.1.2 can ship. The T010 audit doc called this out as Windows-only; I just confirmed it hits on Linux DNS2 too. Repro is below.

What I did locally (uncommitted on DNS2, ready to land once W2 is fixed)

Three files modified, build clean, all unit tests pass logically:

M src/chaindb_migrate.cpp    (H4 fix)
M src/init.cpp               (W1 fix)
M src/test/chaindb_runtime_tests.cpp  (new test)

H4chaindb_migrate.cpp:195 was a bare fs::remove(markerPath); that ignored the return code. Replaced with: non-throwing error_code overload, fs::exists verification after remove, 100ms retry for Windows AV/indexer transient locks, and a hard-fail strError = ...; return false; if the marker still survives. Operator-visible failure beats silent re-migration time bomb.

W1init.cpp:1110 was Lookup("0.0.0.0", addrBind, GetListenPort(), false). Replaced with CService constructed directly from struct 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.

New testmarker_removed_after_successful_migration in chaindb_runtime_tests.cpp. Goes through the real MaybeMigrateLevelDbToRocksDb() end-to-end on the happy path (no pre-existing marker → migration → marker gone). Complements the existing crashed_migration_marker_triggers_retry which only covers the retry path. This is the gap: 18/18 tests passed while the runtime failed because no test exercised the happy path through the real entry point.

The W2 issue I need your help on

The H4 fix cannot be runtime-verified until this is fixed. Repro on DNS2 (Linux, 6.7M record chain):

ChainDB: RocksDB backend active with a legacy LevelDB present
         and a previous migration was interrupted; migrating automatically.
ChainDB migration: removing incomplete previous RocksDB migration
ChainDB migration: copying LevelDB chain state to RocksDB...
ChainDB migration: source=/tmp/tri-h4-clean/txleveldb destination=/tmp/tri-h4-clean/rocksdb
Opening LevelDB in /tmp/tri-h4-clean/txleveldb
Transaction index version is 70509
Opened LevelDB successfully
Opening RocksDB in /tmp/tri-h4-clean/rocksdb
Opened RocksDB successfully
ChainDB migration: copied 100000 / 6771016 records
ChainDB migration: copied 200000 / 6771016 records
...
ChainDB migration: copied 5800000 / 6771016 records
ChainDB migration: copied 5900000 / 6771016 records
ChainDB m[abort]
trianglesd: /root/triangles_v5/src/leveldb/db/version_set.cc:755:
  leveldb::VersionSet::~VersionSet():
  Assertion `dummy_versions_.next_ == &dummy_versions_' failed.

Crashes at ~5.9M / 6.7M records, ~90 seconds in. Dies on the leveldb VersionSet destructor. The assertion is dummy_versions_.next_ == &dummy_versions_ (line 755) — the version-set's circular linked list isn't empty when the destructor runs. A Version is still in the chain.

This is your W2 class of bug: it kills the daemon mid-migration, so fs::remove(markerPath) never runs, and the marker survives on disk. On next startup, init.cpp's fCrashedMigration check re-triggers migration → wipes working data → loop. The H4 fix catches this at the application layer (it now treats a surviving marker as strError = "..."; return false; so the operator sees a loud error), but the deeper problem is the daemon shouldn't be dying in the first place.

The pattern I see:

  1. The migration opens LevelDB as source (line ~110 of chaindb_migrate.cpp)
  2. Opens RocksDB as destination (line ~140)
  3. Copies records in a loop
  4. source.Close() and destination.Close() at line 193-194
  5. Then fs::remove(markerPath) at line 195 (now my fixed version, but this is after the crash)

The crash happens during the copy loop, well before close. Suggests a Version is being added to the leveldb VersionSet during the iterator walk (or during compaction triggered by the writes) and never released. The first 5.9M records work because the version churn is bounded; at some point the deferred cleanup catches up and trips the assertion.

What I need from you

Root-cause and fix the leveldb VersionSet lifetime issue. Specifically:

  • Is CTxDBLevelDB::Close() actually tearing down the env? Or is something holding a Version ref across iterations?
  • Is the migration's iterator (source.NewIterator() at line 33) being properly destroyed each iteration?
  • Are there thread-local / TLS leveldb handles that are leaking?
  • Is this specific to opening both a leveldb and a rocksdb in the same process? (I can't easily test with only one because the migration inherently opens both.)

The same crash hits on the standalone test binary when crashed_migration_marker_triggers_retry runs (pre-existing, not from my changes). The standalone test exits cleanly on small fixtures but the version-set leak accumulates and the assertion fires at process exit.

After W2 is fixed

I have an end-to-end runtime test ready: /tmp/run-h4-patient.sh (240s budget, runs against a fresh copy of DNS2's 2.2M-block chain state). Once W2 is fixed and you push, I can re-run it and either confirm H4 passes at runtime or report what's still broken. The fix is uncommitted locally on DNS2 — I'll commit + push + trigger CI the moment W2 is solid.

Three files, ~80 lines of code, build clean, tests pass logically. The H4 fix is ready to ship the moment W2 is fixed.

Test rig is at /root/triangles_v5/, branch master HEAD f9d1723, uncommitted changes match what I described. Worktree state is clean otherwise.

— Hermes