logos-delivery/docs/analysis/phase2_mutation_audit.md
NagyZoltanPeter 3d98a28d9f
docs(bench): Phase 2 checkpoint + mutation-site audit
- bench_baseline.md: Phase 2 numbers vs Phase 1. Decode/hash counters
  byte-exact unchanged (micro 2/2, macro 6/5), throughput equal-or-better.
  occupied_mem_delta flat by design (retained-memory metric dominated by
  archive-stored messages; refc frees the eliminated transient copies
  deterministically so they never accumulate). No residual deep-copy bug.
- phase2_mutation_audit.md: classification of every field-mutation and
  nil-safety sweep hit (FINE vs FIXED).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 05:03:22 +02:00

65 lines
4.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Phase 2 — `WakuMessage` mutation-site audit
Review artifact for the value-`object``ref object` change. Every hit of the
field-mutation sweep is classified below. Sweep command (plan Step 2.3):
```
grep -rn --include='*.nim' -E '\.(payload|meta|proof|timestamp|ephemeral|version|contentTopic) *=[^=]' logos_delivery library apps
```
plus the nil-safety declaration sweep (plan Step 3):
```
grep -rn --include='*.nim' -E 'var [a-zA-Z_]+: WakuMessage\b|: WakuMessage$' logos_delivery library apps
```
Legend: **FINE** = freshly constructed / owned in the same scope, or not a
`WakuMessage` at all; **FIXED** = mutated a shared/held message under ref
semantics, corrected.
## Field-mutation hits
| # | Site | What it does | Class | Action |
|---|---|---|---|---|
| 1 | `logos_delivery/api/types.nim:81` | `# wm.proof = ...` | FINE | commented-out dead code, no effect |
| 2 | `logos_delivery/waku/rln/proof.nim:93` (`attachRLNProof`) | `msgWithProof.proof = ...` on `var msgWithProof = message` | **FIXED** | outbound path; caller still holds `message`. Changed to `let msgWithProof = message.clone()` |
| 3 | `logos_delivery/waku/rest_api/endpoint/relay/handlers.nim:73` (`attachRlnProofAndValidate`) | `msg.proof = ...` on `var msg = message` | **FIXED** | same aliasing as #2. Changed to `let msg = message.clone()` |
| 4 | `logos_delivery/waku/waku_relay/protocol.nim:680` (`publish`) | `var message = wakuMessage; message.timestamp = ...` | **FIXED** | caller's message mutated. Replaced with `let message = wakuMessage.ensureTimestampSet()` |
| 5 | `logos_delivery/waku/waku_core/message/message.nim:31` (`ensureTimestampSet`) | `result = message; result.timestamp = ...` | **FIXED** | aliased+mutated the shared input. Rewritten to clone-when-unset (non-mutating) |
| 6 | `logos_delivery/waku/waku_core/message/codec.nim:3272` (`decode`) | `msg.payload/…=` on `var msg = WakuMessage()` | FINE | freshly constructed in same scope |
| 7 | `logos_delivery/waku/waku_archive/driver/postgres_driver/postgres_driver.nim:284288` | field writes on `var wakuMessage` | FINE after fix | fresh per loop iteration; needed nil-init (see nil-safety #A) — no cross-row aliasing |
| 8 | `postgres_driver.nim:832` | `m.timestamp = l.timestamp` | FINE | inside a SQL query string literal, not Nim code |
| 9 | `logos_delivery/waku/persistency/sds_persistency.nim:136` | `data.meta = ...` | FINE | `data` is `ChannelData`, not `WakuMessage` |
| 10 | `apps/chat2/chat2.nim:96` | `msg.timestamp = ...` | FINE | `msg` is a `Chat2Message`, not `WakuMessage` |
| 11 | `apps/chat2/chat2.nim:200` | `message.proof = proofRes.get()` | FINE | `message` freshly constructed locally at chat2.nim:184 (`var message = WakuMessage(...)`), owned in scope |
| 12 | `apps/chat2mix/chat2mix.nim:118` | `msg.timestamp = ...` | FINE | `msg` is a `Chat2Message`, not `WakuMessage` |
## Nil-safety hits (`var x: WakuMessage` now defaults to `nil`)
| # | Site | Class | Action |
|---|---|---|---|
| A | `postgres_driver.nim:260``var wakuMessage: WakuMessage` in the row-decode loop | **FIXED** | default-init is now `nil`; following field writes would deref nil. Changed to `= WakuMessage()` |
| B | `not_delivered_storage.nim:22``msg: WakuMessage` field of `TrackedWakuMessage` | FINE | `TrackedWakuMessage` is not constructed on any active path (`archiveMessage` is a stub returning `ok()`); no nil deref reachable |
| C | `recv_service.nim:63``let otherwiseMsg = WakuMessage()` | FINE | explicit construction used as `Option.get` default; non-nil |
## Codec / container decode paths (plan Step 3 verification)
All decoders that populate a `WakuMessage`-typed field construct explicitly, so
none rely on nil default-init:
- `waku_core/message/codec.nim:25``var msg = WakuMessage()`
- `waku_filter_v2/rpc_codec.nim:93``rpc.wakuMessage = ?WakuMessage.decode(message)`
- `waku_lightpush/rpc_codec.nim:38` / `waku_lightpush_legacy/rpc_codec.nim``rpc.message = ?WakuMessage.decode(...)`
- `waku_store/rpc_codec.nim:167/170``keyValue.message = some(?WakuMessage.decode(...))` / `none(WakuMessage)` (Opt wrapper kept, not collapsed to nil)
- sqlite `queries.nim:35` / postgres row decode — `return WakuMessage(...)` / `WakuMessage()`
`Option[WakuMessage]` / `Opt[WakuMessage]` occurrences (store RPC, filter) keep
their wrapper semantics; nil-ref and `none` are **not** conflated.
## Other diagnostic fix
- `logos_delivery/waku/rln/rln.nim:75``msgHash = msg.hash` (a chronicles log
field) resolved to the generic `std/hashes` object hash under value semantics;
a ref no longer matches it. Changed to `msg[].hash` to preserve the exact
prior diagnostic value. Not a mutation; surfaced by the type change.