Skip to content

refactor: make wallet events atomic#116

Open
xdustinface wants to merge 23 commits into
v0.42-devfrom
refactor/wallet-events-atomic
Open

refactor: make wallet events atomic#116
xdustinface wants to merge 23 commits into
v0.42-devfrom
refactor/wallet-events-atomic

Conversation

@xdustinface
Copy link
Copy Markdown
Owner

Reshape WalletEvent so each variant carries the records and post-change balance needed to persist a wallet update atomically off a single event. Replaces TransactionReceived / TransactionStatusChanged / BalanceUpdated with MempoolTransactionReceived, TransactionInstantSendLocked, and BlockProcessChange, adds RecordAction and BlockRecordUpdate for per-record context, and identifies accounts by DerivationPath instead of account_index: u32.

* chore: add Manki AI code review configuration

* fix: pass `claude_code_oauth_token` to Manki action
* feat: add session outcome fields to `PeerReputation`

Adds `last_success`, `last_tried`, and `consecutive_failures` to `PeerReputation`, plus a `record_connection_failure` method on `PeerReputationManager`. All new fields use `#[serde(default)]` so existing `reputations.json` files load without migration.

`record_connection_attempt` now sets `last_tried` and `record_successful_connection` sets `last_success` and resets `consecutive_failures`.

* test: cover `PeerReputation` session outcome transitions

Adds unit tests for default values, `last_tried` on attempt, `last_success` plus `consecutive_failures` reset on success, failure streak increment preserving `last_success`, and legacy `reputations.json` decoding with missing fields.

* feat: bump `AddrV2.time` on successful handshake

Adds `AddrV2Handler::mark_seen` to refresh the stored timestamp for a directly observed peer, preserving existing services for known entries and inserting a fresh entry otherwise. `connect_to_peer` now calls `mark_seen` after a successful handshake so the `peers.dat` time reflects first-hand observation instead of gossip.

* feat: track peer connection outcomes in network manager

Calls `record_connection_failure` on both the TCP connect failure and the handshake failure paths in `PeerNetworkManager::connect_to_peer`, so the `consecutive_failures` streak reflects every unsuccessful attempt.

* refactor: drop backward-compat shims from `PeerReputation`

Removes `#[serde(default)]` on the new session outcome fields and the legacy-JSON load test. Backward compatibility of `reputations.json` across versions is no longer a requirement, so the shims and test are dead weight.

* refactor: thread peer-advertised services into `mark_seen`

* docs: clarify `last_connection` vs `last_tried` on `PeerReputation`

* refactor: extract `make_addr_message` helper in `addrv2`

* refactor: defensively set `last_tried` in `record_connection_failure`

* refactor: add atomic `record_failure_with_penalty` and use at failure sites

* chore: apply `cargo fmt`

* refactor: clamp `consecutive_failures` on deserialization

* refactor: extract `apply_score_change` and always update `last_tried` on failure

* test: cover `record_failure_with_penalty` directly

* refactor: clamp \`consecutive_failures\` at runtime and consolidate failure-field updates

Extract private `record_failure_fields` that applies `last_tried = now` and
`consecutive_failures.saturating_add(1).min(MAX_CONSECUTIVE_FAILURES)`. Both
`record_connection_failure` and `record_failure_with_penalty` now delegate to it,
eliminating duplicated mutations and capping the in-memory streak at the same 1000
limit enforced by the deserializer.

* test: cover \`consecutive_failures\` clamp, \`last_success\` preservation, \`mark_seen\` eviction

- Assert `last_success` is unchanged after `record_failure_with_penalty`
- Deserialize a `PeerReputation` with `consecutive_failures: 99999` and assert it clamps to `MAX_CONSECUTIVE_FAILURES`
- Fill `AddrV2Handler` to capacity and assert `mark_seen` stays bounded and includes the new entry

* refactor: remove unused `record_connection_failure`

* test: stabilize eviction test and cover runtime `consecutive_failures` saturation

* refactor: document and assert non-negative contract on `record_failure_with_penalty`

* test: cover `last_tried` preservation on success and update on failure

* fix: clamp negative `score_change` in `record_failure_with_penalty`

* test: cover happy-path attempt to success lifecycle

* refactor: tighten `clamp_future_system_time` bounds and enforce load invariants

Add a 30-day lower bound to `clamp_future_system_time` so stale or corrupted timestamps
(including epoch 0) are discarded on load, in addition to future ones.

Add `PeerReputation::normalize_after_load` and call it from the storage load path.
It resets `consecutive_failures` to 0 whenever `last_tried` is `None`, preventing
the inconsistent state where a non-zero failure streak has no temporal anchor.

* test: cover `clamp_future_system_time` edge cases

Add three tests: future timestamp rejected, epoch-zero rejected (exercising the new
lower bound), and recent-past timestamp preserved.

* docs: clarify zero-`score_change` contract on `record_failure_with_penalty`

A value of 0 is a deliberate no-op for the reputation score but still records
the failure counter and timestamp, which is useful for failures that should be
tracked without contributing toward a ban.

* fix: use `checked_sub` in `clamp_future_system_time` to avoid panic on broken clocks

* test: cover `normalize_after_load` via storage round-trip

* fix: use `checked_add` in `clamp_future_system_time` to avoid panic on far-future clocks

* test: also cover stale-timestamp path in `normalize_after_load` round-trip

* refactor: drop 30-day stale-timestamp floor from `clamp_future_system_time`

Remove `TIMESTAMP_MAX_AGE` and the `floor` computation that rejected timestamps older than 30 days. The future-timestamp guard (10-second tolerance) is the only meaningful constraint. Update the `normalize_after_load` doc comment to drop the "stale" reference.

* test: remove obsolete stale-timestamp tests and consolidate `clamp_future_system_time` coverage

Delete `test_normalize_after_load_via_storage_round_trip_stale` (tested the removed stale-floor path). Merge the three `test_clamp_future_system_time_*` tests into a single `test_clamp_future_system_time` covering future rejection and recent-past acceptance.

* fix: \`mark_seen\` now overwrites \`services\` on existing entries

Since round 1 the caller passes the actual handshake-negotiated services, so
preserving the gossip-sourced value was inverted. The handshake-observed value
is authoritative and is now written on both new and existing entries.

Rename \`test_mark_seen_bumps_time_and_preserves_services\` to
\`test_mark_seen_bumps_time_and_updates_services\` and update its assertion to
expect the handshake services, not the original gossip services.

* test: cover positive \`normalize_after_load\` branch

Add \`test_normalize_after_load_preserves_failures_when_last_tried_valid\`
to assert that a valid (non-future) \`last_tried\` and a non-zero
\`consecutive_failures\` are both preserved through the load round-trip,
complementing the existing reset-path test.

* refactor: move `record_failure_fields` to `impl PeerReputation`

* refactor: move `apply_score_change` to `impl PeerReputation`
@manki-review
Copy link
Copy Markdown

manki-review Bot commented Apr 27, 2026

Manki — Review complete

Planner (17s)
    refactor · 1923 lines · 3 agents
    review effort: medium · judge effort: medium

Review — 12 findings
    ✅ Correctness & Logic — 0 (560s)
    ✅ Security & Safety — 5 (102s)
    ✅ Testing & Coverage — 7 (149s)

Judge — 12 kept · 0 dropped (30s)
    kept: 2 warning · 3 suggestion · 7 nitpick

Review metadata

Config:

  • Models: reviewer=claude-sonnet-4-6, judge=claude-opus-4-7
  • Review level: large (auto, 1923 lines)
  • Team: Correctness & Logic, Security & Safety, Testing & Coverage
  • Memory: disabled
  • Nit handling: issues

Judge decisions:

  • ✓ Kept: "Missing null-check on txid before dereference in FFI callback" (nitpick, high confidence) — "Impact: High (UB on null deref from C), Likelihood: Possible (only if C caller misuses API; Rust dispatch always passes valid). Other callbacks in this surface check pointers, so consistency matters."
  • ✓ Kept: "Silent CString truncation swallows derivation-path data on embedded NUL" (nitpick, medium confidence) — "Impact: High (data integrity — wrong account association), Likelihood: Unlikely (well-formed BIP-32 paths never contain NUL). Worth fixing for defense-in-depth given the data-integrity consequences."
  • ✓ Kept: "Unchecked u32 truncation of ffi_updates.len() passed to C callback" (nitpick, high confidence) — "Impact: High if it occurred (C reads short slice), Likelihood: Unlikely (a single block cannot contain 4B+ wallet-relevant updates). Downgrading because the bound is physically unreachable, but try_from is cheap defensive coding."
  • ✓ Kept: "Explicit drop calls after callback are redundant and could mislead readers" (nitpick, high confidence) — "Impact: Low (readability), Likelihood: Certain (always runs). Cosmetic; the explicit drops add minor noise but no harm."
  • ✓ Kept: "FFIBlockRecordUpdate contains a raw pointer but lacks Send/Sync markers" (nitpick, high confidence) — "Impact: Low (consistency only), Likelihood: Unlikely (struct only used in synchronous callback scope). Reviewer themselves noted no immediate hazard."
  • ✓ Kept: "SyncedHeightUpdated callback tracked but never asserted in integration tests" (warning, high confidence) — "Impact: High (regression that silences the persistence checkpoint boundary would be undetected), Likelihood: Probable (any future refactor could break it silently). Concrete test gap."
  • ✓ Kept: "No unit test for late IS-lock arriving after block confirmation" (suggestion, medium confidence) — "Impact: Medium (incorrect event emission could confuse consumers), Likelihood: Possible. The old test was removed without replacement; worth adding but not strictly a correctness defect in current code."
  • ✓ Kept: "No test for BlockProcessChange with empty updates (coinbase maturation / balance-only change)" (suggestion, medium confidence) — "Impact: Medium (documented code path untested), Likelihood: Possible. Coinbase-maturation balance-only path is a documented contract; missing coverage but not a current bug."
  • ✓ Kept: "mempool_received_count never asserted to be zero during initial historical block sync" (warning, high confidence) — "Impact: High (cross-path contamination would silently pass), Likelihood: Probable for any regression. Trivial assert that locks down a meaningful invariant."
  • ✓ Kept: "Timing assumption in post-sync transaction test may produce flaky results" (suggestion, medium confidence) — "Impact: Medium (test flakiness), Likelihood: Possible in regtest. Reasonable concern about wait gating; refactor would harden the test."
  • ✓ Kept: "block_process_change_record_count incremented inside mutex critical section" (nitpick, high confidence) — "Impact: Low (test contention only), Likelihood: Possible. Test-only micro-optimization; no correctness issue."
  • ✓ Kept: "on_transaction_instant_send_locked discards islock_data without any validation" (nitpick, high confidence) — "Impact: Low (test thoroughness), Likelihood: N/A. Adding an assertion would strengthen the test but isn't a defect."

Timing:

  • Parse: 1.6s
  • Review agents: 579.7s
  • Judge: 29.9s
  • Total: 611.2s

manki-review[bot]
manki-review Bot previously requested changes Apr 27, 2026
Copy link
Copy Markdown

@manki-review manki-review Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Atomic wallet event refactor is solid, but the FFI surface has gaps: a missing null-check on a callback txid pointer, silent CString/u32 truncation on derivation paths and update counts, and test coverage that logs but doesn't assert key boundaries (mempool=0 during sync, SyncedHeightUpdated firing).

📊 12 findings (2 warning, 3 suggestion, 7 nitpick) · 1923 lines · 611s

Review stats
{
  "model": "claude-sonnet-4-6",
  "reviewTimeMs": 611210,
  "diffLines": 1923,
  "diffAdditions": 1018,
  "diffDeletions": 905,
  "filesReviewed": 16,
  "agents": [
    "Correctness & Logic",
    "Security & Safety",
    "Testing & Coverage"
  ],
  "findingsRaw": 12,
  "findingsKept": 12,
  "findingsDropped": 0,
  "severity": {
    "blocker": 0,
    "warning": 2,
    "suggestion": 3,
    "nitpick": 7
  },
  "verdict": "REQUEST_CHANGES",
  "prNumber": 116,
  "commitSha": "1f20837af475544fe18f1df92aa136724496cbe3",
  "agentMetrics": [
    {
      "name": "Correctness & Logic",
      "findingsRaw": 0,
      "findingsKept": 0,
      "responseLength": 1388
    },
    {
      "name": "Security & Safety",
      "findingsRaw": 5,
      "findingsKept": 5,
      "responseLength": 4387
    },
    {
      "name": "Testing & Coverage",
      "findingsRaw": 7,
      "findingsKept": 7,
      "responseLength": 7085
    }
  ],
  "judgeMetrics": {
    "confidenceDistribution": {
      "high": 8,
      "medium": 4,
      "low": 0
    },
    "severityChanges": 12,
    "mergedDuplicates": 0,
    "defensiveHardeningCount": 3,
    "verdictReason": "novel_suggestion"
  },
  "fileMetrics": {
    "fileTypes": {
      ".rs": 15,
      ".toml": 1
    },
    "findingsPerFile": {
      "dash-spv-ffi/src/bin/ffi_cli.rs": 1,
      "dash-spv-ffi/src/callbacks.rs": 4,
      "dash-spv-ffi/tests/dashd_sync/tests_callback.rs": 3,
      "key-wallet-manager/src/event_tests.rs": 2,
      "dash-spv-ffi/tests/dashd_sync/callbacks.rs": 2
    }
  },
  "reviewerModel": "claude-sonnet-4-6",
  "judgeModel": "claude-opus-4-7"
}

Comment thread dash-spv-ffi/tests/dashd_sync/tests_callback.rs
Comment thread key-wallet-manager/src/event_tests.rs
Comment thread key-wallet-manager/src/event_tests.rs
Comment thread dash-spv-ffi/tests/dashd_sync/tests_callback.rs
Comment thread dash-spv-ffi/tests/dashd_sync/tests_callback.rs
* refactor(key-wallet-manager): track wallet heights per wallet

Move committed sync height into wallet metadata and expose it through `WalletInfoInterface` so `WalletManager` no longer relies on global cached heights.

Compute manager-level synced_height and tip_height as the minimum across wallet infos, propagate committed sync updates per wallet, and keep tip_height as per-wallet applied chain state.

Update tests and example messaging to match the new aggregation semantics.

* enforce `Send + Sync + 'static` on generic type of the wallet manager
@github-actions
Copy link
Copy Markdown

This PR has merge conflicts with the base branch. Please rebase or merge the base branch into your branch to resolve them.

xdustinface added a commit that referenced this pull request Apr 27, 2026
`test_callbacks_post_sync_transactions_and_disconnect` mined a block
immediately after `send_to_address`, so the SPV node could process the
transaction via the block path before observing it in the mempool. The
later `wait_for_callback` for `mempool_transaction_received_count` would
then time out and the assertion that the mempool callback fired would
fail. Wait for the mempool callback before calling `generate_blocks` so
the mempool path is exercised deterministically.

Addresses manki-review review comment on PR [#116](#116):
#116 (comment)
xdustinface added a commit that referenced this pull request Apr 27, 2026
`mempool_received_count` was loaded and logged in
`test_all_callbacks_during_sync` but never asserted. During initial
block-chain replay, `MempoolTransactionReceived` must not fire for
historical transactions (they arrive via `BlockProcessChange`). Without
the assertion, a regression that fired the mempool callback for every
confirmed transaction would pass undetected. Add the explicit zero
assertion to enforce the cross-path boundary.

Addresses manki-review review comment on PR [#116](#116):
#116 (comment)
xdustinface added a commit that referenced this pull request Apr 27, 2026
`synced_height_updated_count` and `last_synced_height` were tracked but
never asserted in `test_all_callbacks_during_sync`. `SyncedHeightUpdated`
is the atomicity boundary consumers use to safely flush their persistence
layer after a filter batch — a regression that silenced the callback
would let consumers stop checkpointing and the test would still pass.
Assert the counter advances and the reported height matches
`dashd.initial_height` so the callback contract is enforced.

Addresses manki-review review comment on PR [#116](#116):
#116 (comment)
Reshape `WalletEvent` so each variant carries the records and post-change balance needed to persist a wallet update atomically off a single event.
Replaces `TransactionReceived` / `TransactionStatusChanged` / `BalanceUpdated` with `MempoolTransactionReceived`, `TransactionInstantSendLocked`, and `BlockProcessChange`, adds `RecordAction` and `BlockRecordUpdate` for per-record context, and identifies accounts by `DerivationPath` instead of `account_index: u32`.
`test_callbacks_post_sync_transactions_and_disconnect` mined a block
immediately after `send_to_address`, so the SPV node could process the
transaction via the block path before observing it in the mempool. The
later `wait_for_callback` for `mempool_transaction_received_count` would
then time out and the assertion that the mempool callback fired would
fail. Wait for the mempool callback before calling `generate_blocks` so
the mempool path is exercised deterministically.

Addresses manki-review review comment on PR [#116](#116):
#116 (comment)
`mempool_received_count` was loaded and logged in
`test_all_callbacks_during_sync` but never asserted. During initial
block-chain replay, `MempoolTransactionReceived` must not fire for
historical transactions (they arrive via `BlockProcessChange`). Without
the assertion, a regression that fired the mempool callback for every
confirmed transaction would pass undetected. Add the explicit zero
assertion to enforce the cross-path boundary.

Addresses manki-review review comment on PR [#116](#116):
#116 (comment)
`synced_height_updated_count` and `last_synced_height` were tracked but
never asserted in `test_all_callbacks_during_sync`. `SyncedHeightUpdated`
is the atomicity boundary consumers use to safely flush their persistence
layer after a filter batch — a regression that silenced the callback
would let consumers stop checkpointing and the test would still pass.
Assert the counter advances and the reported height matches
`dashd.initial_height` so the callback contract is enforced.

Addresses manki-review review comment on PR [#116](#116):
#116 (comment)
@xdustinface xdustinface force-pushed the refactor/wallet-events-atomic branch from cf668fd to ebf8d81 Compare April 27, 2026 01:48
@manki-review
Copy link
Copy Markdown

manki-review Bot commented Apr 27, 2026

Manki — Review complete

Planner (17s)
    refactor · 1959 lines · 5 agents
    review effort: medium · judge effort: high

Review — 26 findings
    ✅ Correctness & Logic — 4 (350s)
    ✅ Security & Safety — 5 (165s)
    ✅ Architecture & Design — 5 (270s)
    ✅ Testing & Coverage — 6 (137s)
    ✅ Dependencies & Integration — 6 (110s)

Judge — 19 kept · 0 dropped (108s)
    kept: 6 warning · 6 suggestion · 7 nitpick

Review metadata

Config:

  • Models: reviewer=claude-sonnet-4-6, judge=claude-opus-4-7
  • Review level: large (auto, 1959 lines)
  • Team: Correctness & Logic, Security & Safety, Architecture & Design, Testing & Coverage, Dependencies & Integration
  • Memory: disabled
  • Nit handling: issues

Judge decisions:

  • ✓ Kept: "Unconditional unsafe txid dereference without null check in FFI callback" (nitpick, medium confidence) — "Impact: High (UB if C caller passes null), Likelihood: Possible (the only in-tree caller is the Rust dispatcher which always passes a valid pointer, but it's an extern "C" function that could be invoked from C/Swift). Other callbacks in the same file null-guard, so consistency argues for adding the check. CLI demo code, not library code."
  • ✓ Kept: "Race condition between block_process_change_record_count and block_process_change_count assertions" (warning, medium confidence) — "Impact: Medium (flaky test, not production code), Likelihood: Possible (depends on memory ordering between the two stores from the same thread — actually since both happen on the same callback thread sequentially, the SeqCst ordering does guarantee visibility once any later SeqCst load on the same thread sees the first store; cross-thread the wait_for_callback observer could indeed race). The suggested fix (wait on the count that increments last) is straightforward and removes any ambiguity."
  • ✓ Kept: "Unchecked usize-to-u32 narrowing cast for update_count in FFI dispatch" (nitpick, high confidence) — "Impact: High in theory (silent data loss), Likelihood: Unlikely (no real block contains >4B wallet records). Merged findings 3, 7, and 26 — three reviewers flagged the same cast. Worth a try_from but not blocking."
  • ✓ Kept: "Redundant explicit drop calls in BlockProcessChange dispatch arm" (nitpick, high confidence) — "Impact: Low (cosmetic), Likelihood: Certain (always present). Merged findings 4, 6, 13, and 25 — four reviewers flagged the same redundant drops. Rust's reverse-declaration drop order already gives the correct ordering; the explicit drops are noise but not incorrect."
  • ✓ Kept: "FFIBlockRecordUpdate contains borrowed pointers that C consumers can accidentally dangle" (suggestion, medium confidence) — "Impact: High (UB if a consumer caches the struct), Likelihood: Possible (common pattern for Swift consumers building arrays from pointer+count). Documentation already says 'Borrowed for the duration of the callback' but a more prominent header comment on the struct itself would reduce misuse risk."
  • ✓ Kept: "InstantLock bytes serialized but never validated in any callback consumer" (nitpick, low confidence) — "Impact: Medium (silent IS-lock corruption), Likelihood: Unlikely (serialization path is well-tested). A debug-assert round-trip would help catch regressions but isn't required."
  • ✓ Kept: "CString null-byte errors silently produce empty account path strings" (nitpick, medium confidence) — "Impact: Medium (misidentification of source account), Likelihood: Unlikely (DerivationPath::to_string() never produces null bytes today). Merged findings 9 and 24 — two reviewers flagged. .expect() or explicit log+skip would be safer than silent empty-string fallback."
  • ✓ Kept: "Lifecycle unit tests removed without equivalent deterministic coverage" (warning, high confidence) — "Impact: High (loss of regression coverage for state-machine invariants), Likelihood: Probable (these invariants — duplicate suppression, late-IS-after-confirm, mempool→IS→block ordering — are exactly the surface area being refactored). Merged findings 10 and 16 — two reviewers independently flagged. The dashd integration tests are slow and non-deterministic; deterministic unit-level coverage of these transitions should be retained. This also aligns with the existing open thread PRRT_kwDOQSlaXs59tErE about late IS-lock arriving after block confirmation."
  • ✓ Kept: "MempoolTransactionReceived carries a single account_path, silently discarding other affected accounts" (warning, medium confidence) — "Impact: High (mempool tx affecting multiple accounts only reports one), Likelihood: Possible (cross-account change outputs are realistic in HD wallets). Contradicts the PR's atomicity goal and creates inconsistent shape vs BlockProcessChange. Worth addressing before the API ships."
  • ✓ Kept: "SyncedHeightUpdated carries no balance snapshot, requiring consumers to track cross-event state" (suggestion, medium confidence) — "Impact: Medium (consumer complexity, possible inconsistency under restart), Likelihood: Probable (any wallet with multi-block sync gaps). Adding a balance field is a reasonable design improvement but not a correctness bug."
  • ✓ Kept: "on_block_process_change test callback increments atomic counter while holding the record mutex" (nitpick, high confidence) — "Impact: Low (test code, mild lock contention), Likelihood: Certain. Atomic doesn't need mutex protection; widening the critical section is a minor smell, not a bug."
  • ✓ Kept: "FFIRecordAction (Inserted/Updated) never asserted in any test" (warning, high confidence) — "Impact: Medium (regression in action tagging would go undetected), Likelihood: Probable (action semantics are central to this PR). The PR introduces the Inserted/Updated distinction as a primary atomicity signal; no test verifies the tag value."
  • ✓ Kept: "TransactionInstantSendLocked event has no unit test" (warning, high confidence) — "Impact: Medium (dispatch path only covered by slow integration tests), Likelihood: Probable. Aligns with open thread PRRT_kwDOQSlaXs59tErE asking for late-IS-after-confirmation coverage. New event variant deserves direct unit coverage."
  • ✓ Kept: "BlockProcessChange with zero updates passes non-null dangling pointer to C" (suggestion, medium confidence) — "Impact: Medium (UB for consumers checking only NULL), Likelihood: Possible (coinbase maturation triggers balance-only changes). Matches open thread PRRT_kwDOQSlaXs59tErI. Passing std::ptr::null() when empty makes the contract unambiguous."
  • ✓ Kept: "account_path field never asserted in FFI integration tests" (suggestion, medium confidence) — "Impact: Low (test gap), Likelihood: Certain. Since account_path replaces the well-tested account_index, asserting on the path string would prevent silent regressions."
  • ✓ Kept: "last_synced_height == dashd.initial_height may be brittle" (nitpick, low confidence) — "Impact: Low (potential test flake), Likelihood: Unlikely (current sync semantics commit at tip). >= would be more resilient but the strict equality also documents the intended invariant."
  • ✓ Kept: "Breaking ABI change in repr(C) FFIWalletEventCallbacks struct" (suggestion, high confidence) — "Impact: High in theory, Likelihood: Unlikely to cause silent corruption (cbindgen header regeneration is the standard process and downstream consumers must recompile). API is documented as unstable (0.x). The behavior is intentional for this refactor; not a correctness issue, but a release-note item."
  • ✓ Kept: "API contract break: account_index (u32) replaced by derivation-path string in OnMempoolTransactionReceivedCallback" (suggestion, high confidence) — "Impact: Medium (forces consumer migration), Likelihood: Certain. Intentional refactor with type signature change — consumers will get compile errors on the new pointer type, not silent miscompilation. Migration documentation would be helpful but the change itself is sound."
  • ✓ Kept: "Semantic ambiguity: 'spendable' renamed to 'confirmed' in FFIBalance may change consumer behaviour" (warning, medium confidence) — "Impact: High (consumers displaying 'confirmed' as spendable would inflate the user-visible balance and attempt unsendable transactions), Likelihood: Probable (the rename masquerades as cosmetic but changes semantics — old 'spendable' excluded immature/locked, new 'confirmed' may not). Needs explicit FFI-boundary documentation of the relationship to old spendable() and migration guidance."

Timing:

  • Parse: 1.5s
  • Review agents: 369.9s
  • Judge: 108.2s
  • Total: 479.7s

@manki-review manki-review Bot dismissed their stale review April 27, 2026 01:48

Superseded by new review

manki-review[bot]
manki-review Bot previously requested changes Apr 27, 2026
Copy link
Copy Markdown

@manki-review manki-review Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Atomicity refactor lands cleanly but most reviewer noise is duplicates around the same three FFI dispatch lines (redundant drops, u32-truncation cast, CString::new fallbacks). Real concerns: removed lifecycle unit tests have no deterministic replacement, and the spendable→confirmed semantic shift in FFIBalance can silently inflate displayed balances for downstream consumers.

📊 19 findings (6 warning, 6 suggestion, 7 nitpick) · 1959 lines · 480s

Review stats
{
  "model": "claude-sonnet-4-6",
  "reviewTimeMs": 479677,
  "diffLines": 1959,
  "diffAdditions": 1048,
  "diffDeletions": 911,
  "filesReviewed": 16,
  "agents": [
    "Correctness & Logic",
    "Security & Safety",
    "Architecture & Design",
    "Testing & Coverage",
    "Dependencies & Integration"
  ],
  "findingsRaw": 26,
  "findingsKept": 19,
  "findingsDropped": 7,
  "severity": {
    "blocker": 0,
    "warning": 6,
    "suggestion": 6,
    "nitpick": 7
  },
  "verdict": "REQUEST_CHANGES",
  "prNumber": 116,
  "commitSha": "ebf8d81464456177490a614e32928f7c50fc8d3c",
  "agentMetrics": [
    {
      "name": "Correctness & Logic",
      "findingsRaw": 4,
      "findingsKept": 4,
      "responseLength": 4069
    },
    {
      "name": "Security & Safety",
      "findingsRaw": 5,
      "findingsKept": 3,
      "responseLength": 6458
    },
    {
      "name": "Architecture & Design",
      "findingsRaw": 5,
      "findingsKept": 5,
      "responseLength": 5564
    },
    {
      "name": "Testing & Coverage",
      "findingsRaw": 6,
      "findingsKept": 6,
      "responseLength": 6559
    },
    {
      "name": "Dependencies & Integration",
      "findingsRaw": 6,
      "findingsKept": 3,
      "responseLength": 5621
    }
  ],
  "judgeMetrics": {
    "confidenceDistribution": {
      "high": 8,
      "medium": 9,
      "low": 2
    },
    "severityChanges": 19,
    "mergedDuplicates": 7,
    "defensiveHardeningCount": 3,
    "verdictReason": "novel_suggestion"
  },
  "fileMetrics": {
    "fileTypes": {
      ".rs": 15,
      ".toml": 1
    },
    "findingsPerFile": {
      "dash-spv-ffi/src/bin/ffi_cli.rs": 1,
      "dash-spv-ffi/tests/dashd_sync/tests_callback.rs": 3,
      "dash-spv-ffi/src/callbacks.rs": 9,
      "key-wallet-manager/src/event_tests.rs": 2,
      "key-wallet-manager/src/events.rs": 1,
      "dash-spv-ffi/tests/dashd_sync/callbacks.rs": 3
    }
  },
  "reviewerModel": "claude-sonnet-4-6",
  "judgeModel": "claude-opus-4-7"
}

Comment thread dash-spv-ffi/tests/dashd_sync/tests_callback.rs Outdated
Comment thread dash-spv-ffi/src/callbacks.rs Outdated
Comment thread key-wallet-manager/src/event_tests.rs
Comment thread key-wallet-manager/src/events.rs
Comment thread dash-spv-ffi/src/callbacks.rs
Comment thread dash-spv-ffi/src/callbacks.rs
Comment thread dash-spv-ffi/tests/dashd_sync/tests_callback.rs
Comment thread dash-spv-ffi/src/callbacks.rs
Comment thread dash-spv-ffi/src/callbacks.rs
Comment thread dash-spv-ffi/tests/dashd_sync/callbacks.rs
The per-wallet semantics from #117 keep `synced_height` and `last_processed_height` independent. The rebase reintroduced a manager-level bump that mirrored the trait default, contradicting the test and per-wallet model.
@manki-review
Copy link
Copy Markdown

manki-review Bot commented Apr 27, 2026

Manki — Review complete

Planner (24s)
    refactor · 1955 lines · 4 agents
    review effort: medium · judge effort: medium

Review — 34 findings
    ✅ Security & Safety — 14 (200s)
    ✅ Correctness & Logic — 4 (338s)
    ✅ Testing & Coverage — 6 (178s)
    ✅ Architecture & Design — 10 (193s)

Judge — 18 kept · 0 dropped (102s)
    kept: 7 warning · 6 suggestion · 5 nitpick

Review metadata

Config:

  • Models: reviewer=claude-sonnet-4-6, judge=claude-opus-4-7
  • Review level: large (auto, 1955 lines)
  • Team: Security & Safety, Correctness & Logic, Testing & Coverage, Architecture & Design
  • Memory: disabled
  • Nit handling: issues

Judge decisions:

  • ✓ Kept: "Race condition between block_process_change_record_count and block_process_change_count assertions" (warning, high confidence) — "Flagged by 3 reviewers. Two separate atomics with no joint ordering — wait on one then read the other can observe stale zero. Impact: Medium (test flakiness), Likelihood: Possible → warning."
  • ✓ Kept: "MempoolTransactionReceived carries a single account_path, silently discarding other affected accounts" (warning, high confidence) — "Flagged by 2 reviewers. Single account_path silently drops additional affected accounts on multi-account txs, asymmetric with BlockProcessChange's slice. Impact: High (silent data loss in consumer persistence), Likelihood: Possible → warning."
  • ✓ Kept: "Lifecycle unit tests removed without equivalent deterministic coverage" (warning, high confidence) — "Flagged by 3 reviewers independently — strong consensus. Removed duplicate-suppression and lifecycle ordering tests are exactly the regressions that produce double-credits/missed updates. Impact: High, Likelihood: Probable → warning (merged with finding 19, 28)."
  • ✓ Kept: "FFIRecordAction (Inserted/Updated) never asserted in any integration test" (warning, high confidence) — "Flagged by 2 reviewers. Action discriminant (Inserted/Updated) is never validated, a swap bug is invisible. Impact: Medium, Likelihood: Possible → warning (merged with finding 22)."
  • ✓ Kept: "TransactionInstantSendLocked event has no unit test" (warning, high confidence) — "Flagged by 2 reviewers. New IS-lock event path has zero deterministic coverage. Impact: High (financial-data path), Likelihood: Possible → warning (merged with finding 20)."
  • ✓ Kept: "Semantic ambiguity: 'spendable' renamed to 'confirmed' in FFIBalance may change consumer behaviour" (suggestion, medium confidence) — "Single reviewer; the rename clarifies semantics but consumers must read docs. Test passes in current context. Impact: Medium, Likelihood: Possible → suggestion."
  • ✓ Kept: "BlockProcessChange with zero updates passes non-null dangling pointer to C" (suggestion, high confidence) — "Flagged by 2 reviewers. Vec::as_ptr on empty Vec returns dangling sentinel; convention violation but disciplined consumers guard on count. Impact: Low-Medium, Likelihood: Possible → suggestion."
  • ✓ Kept: "Breaking ABI change in repr(C) FFIWalletEventCallbacks struct" (warning, high confidence) — "Flagged by 2 reviewers. repr(C) field layout changed — old binaries will misroute callbacks. For an FFI library consumed by iOS/Swift, this is a real concern requiring migration notes/version bump. Impact: High, Likelihood: Probable for un-recompiled consumers → warning (upgraded from suggestion)."
  • ✓ Kept: "API contract break: account_index (u32) replaced by derivation-path string in OnMempoolTransactionReceivedCallback" (suggestion, high confidence) — "Flagged by 2 reviewers. Old-header consumers will misinterpret pointer as u32. Same migration concern as ABI break above; documenting migration is the fix. Suggestion is appropriate."
  • ✓ Kept: "SyncedHeightUpdated callback carries no balance snapshot, requiring consumers to correlate cross-event state" (suggestion, medium confidence) — "Single reviewer. Design observation — would make checkpoint self-sufficient but current shape is workable. Suggestion appropriate."
  • ✓ Kept: "read_balance hides unsafe pointer dereference without safety contract" (nitpick, medium confidence) — "Single reviewer. Internal ffi_cli helper, not a public API; making it unsafe fn is a style preference. Nitpick."
  • ✓ Kept: "short_wallet panics on multi-byte UTF-8 at byte position 8" (nitpick, high confidence) — "Hex-encoded wallet IDs are ASCII so unreachable in practice. Reviewer acknowledges as much. Hypothetical."
  • ✓ Kept: "SyncedHeightUpdated assertions read without a preceding wait, risking stale or zero values" (warning, high confidence) — "Single reviewer but specific and concrete: counter is read without a wait_for_callback, leading to flake or false-pass. Impact: Medium, Likelihood: Possible → warning."
  • ✓ Kept: "CString fallback to empty string on interior-null silently corrupts account_path in FFI callbacks" (nitpick, medium confidence) — "Interior NUL in a derivation-path string would require corruption of internal trusted code. Hypothetical reachability — but logging the failure is a cheap defensive improvement."
  • ✓ Kept: "ffi_updates.len() as u32 silently truncates on 64-bit platforms" (nitpick, high confidence) — "A single block carrying >4B records is impossible. Reviewer agrees this is unrealistic. Hypothetical."
  • ✓ Kept: "Explicit drop ordering in BlockProcessChange dispatch is correct but misleading" (nitpick, high confidence) — "Flagged by 2 reviewers as cosmetic — the drops are correct but redundant given Rust drop order (merged with finding 34). Style preference."
  • ✓ Kept: "received_transactions aggregates mempool and block records, weakening per-callback assertions" (suggestion, medium confidence) — "Single reviewer. Test cross-contamination concern — separate buckets would make assertions stronger. Test improvement, not a correctness bug."
  • ✓ Kept: "MempoolTransactionReceived name misleading when transaction arrives IS-locked" (suggestion, medium confidence) — "Single reviewer. Naming/clarity preference — variant fires for IS-locked first-sightings too. Discussion-worthy but not a bug."

Timing:

  • Parse: 2.5s
  • Review agents: 368.0s
  • Judge: 102.3s
  • Total: 472.8s

@manki-review manki-review Bot dismissed their stale review April 27, 2026 07:10

Superseded by new review

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Apr 27, 2026

Codecov Report

❌ Patch coverage is 61.99095% with 84 lines in your changes missing coverage. Please review.
✅ Project coverage is 70.26%. Comparing base (53b25c4) to head (b374bb2).
⚠️ Report is 1 commits behind head on v0.42-dev.

Files with missing lines Patch % Lines
dash-spv-ffi/src/bin/ffi_cli.rs 0.00% 46 Missing ⚠️
key-wallet-manager/src/events.rs 0.00% 17 Missing ⚠️
dash-spv-ffi/src/callbacks.rs 83.05% 10 Missing ⚠️
key-wallet-manager/src/process_block.rs 89.23% 7 Missing ⚠️
key-wallet-manager/src/lib.rs 87.50% 4 Missing ⚠️
Additional details and impacted files
@@              Coverage Diff              @@
##           v0.42-dev     #116      +/-   ##
=============================================
- Coverage      70.50%   70.26%   -0.25%     
=============================================
  Files            319      319              
  Lines          66758    66832      +74     
=============================================
- Hits           47071    46960     -111     
- Misses         19687    19872     +185     
Flag Coverage Δ
core 75.81% <ø> (ø)
ffi 44.38% <46.66%> (-1.85%) ⬇️
rpc 20.00% <ø> (ø)
spv 86.78% <ø> (+0.11%) ⬆️
wallet 68.82% <75.86%> (-0.01%) ⬇️
Files with missing lines Coverage Δ
key-wallet-manager/src/accessors.rs 51.21% <100.00%> (-10.66%) ⬇️
key-wallet-manager/src/test_helpers.rs 100.00% <ø> (ø)
...wallet/src/transaction_checking/account_checker.rs 67.02% <100.00%> (+0.51%) ⬆️
...-wallet/src/transaction_checking/wallet_checker.rs 99.28% <ø> (ø)
key-wallet-manager/src/lib.rs 61.41% <87.50%> (+0.40%) ⬆️
key-wallet-manager/src/process_block.rs 90.18% <89.23%> (-1.53%) ⬇️
dash-spv-ffi/src/callbacks.rs 75.74% <83.05%> (-4.87%) ⬇️
key-wallet-manager/src/events.rs 0.00% <0.00%> (ø)
dash-spv-ffi/src/bin/ffi_cli.rs 0.00% <0.00%> (ø)

... and 20 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

manki-review[bot]
manki-review Bot previously requested changes Apr 27, 2026
Copy link
Copy Markdown

@manki-review manki-review Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Major FFI refactor for atomic wallet events — new event shape solves the cross-event ordering hazard but introduces an ABI break, several FFI pointer-lifetime concerns, and significant test-coverage regressions. Lifecycle/duplicate-suppression unit tests were deleted without deterministic replacements (4 reviewers flagged related issues).

📊 18 findings (7 warning, 6 suggestion, 5 nitpick) · 1955 lines · 473s

Review stats
{
  "model": "claude-sonnet-4-6",
  "reviewTimeMs": 472776,
  "diffLines": 1955,
  "diffAdditions": 1044,
  "diffDeletions": 911,
  "filesReviewed": 16,
  "agents": [
    "Security & Safety",
    "Correctness & Logic",
    "Testing & Coverage",
    "Architecture & Design"
  ],
  "findingsRaw": 34,
  "findingsKept": 18,
  "findingsDropped": 16,
  "severity": {
    "blocker": 0,
    "warning": 7,
    "suggestion": 6,
    "nitpick": 5
  },
  "verdict": "REQUEST_CHANGES",
  "prNumber": 116,
  "commitSha": "3747aa8da50bb7ec9d429603a11d8b3636f038f9",
  "agentMetrics": [
    {
      "name": "Security & Safety",
      "findingsRaw": 14,
      "findingsKept": 12,
      "responseLength": 10896
    },
    {
      "name": "Correctness & Logic",
      "findingsRaw": 4,
      "findingsKept": 4,
      "responseLength": 4996
    },
    {
      "name": "Testing & Coverage",
      "findingsRaw": 6,
      "findingsKept": 5,
      "responseLength": 6448
    },
    {
      "name": "Architecture & Design",
      "findingsRaw": 10,
      "findingsKept": 8,
      "responseLength": 10288
    }
  ],
  "judgeMetrics": {
    "confidenceDistribution": {
      "high": 12,
      "medium": 6,
      "low": 0
    },
    "severityChanges": 18,
    "mergedDuplicates": 16,
    "defensiveHardeningCount": 1,
    "verdictReason": "novel_suggestion"
  },
  "fileMetrics": {
    "fileTypes": {
      ".rs": 15,
      ".toml": 1
    },
    "findingsPerFile": {
      "dash-spv-ffi/tests/dashd_sync/tests_callback.rs": 2,
      "dash-spv-ffi/src/callbacks.rs": 8,
      "key-wallet-manager/src/event_tests.rs": 2,
      "dash-spv-ffi/tests/dashd_sync/callbacks.rs": 3,
      "dash-spv-ffi/src/bin/ffi_cli.rs": 2,
      "dash-spv/tests/dashd_sync/helpers.rs": 1
    }
  },
  "reviewerModel": "claude-sonnet-4-6",
  "judgeModel": "claude-opus-4-7"
}

Comment thread dash-spv-ffi/tests/dashd_sync/tests_callback.rs
Comment thread dash-spv-ffi/src/callbacks.rs
Comment thread key-wallet-manager/src/event_tests.rs
Comment thread dash-spv-ffi/tests/dashd_sync/callbacks.rs
Comment thread key-wallet-manager/src/event_tests.rs
Comment thread dash-spv-ffi/src/callbacks.rs
Comment thread dash-spv-ffi/src/callbacks.rs
Comment thread dash-spv-ffi/tests/dashd_sync/tests_callback.rs
Comment thread dash-spv-ffi/tests/dashd_sync/callbacks.rs
Comment thread dash-spv/tests/dashd_sync/helpers.rs
Empty `Vec::as_ptr()` returns a non-null, unspecified pointer. The C/Swift
callback should receive `NULL` paired with `update_count = 0` to follow
standard C-API conventions.

Addresses manki review on PR #116
#116 (comment)
Splits the shared `received_transactions` bucket into per-callback buckets
so a broken mempool callback can't be masked by the block path's success
(or vice versa). Adds tracker fields and assertions for `FFIRecordAction`
discriminants and BIP-32 account path strings, both of which were
delivered by the new atomic events but never inspected in tests.

Increments `block_process_change_count` inside the same lock as
`block_process_change_record_count` so a test waiting on either counter
is guaranteed to see a consistent view of the other. Adds an explicit
`wait_for_callback` for `synced_height_updated` before reading
`last_synced_height`, since that callback is dispatched separately from
`BlockProcessChange` on the same broadcast channel.

Addresses manki review on PR #116
#116 (comment)
#116 (comment)
#116 (comment)
#116 (comment)
#116 (comment)
The implementation currently downgrades a confirmed record's context from
`InBlock(_)` back to `InstantSend(_)` when a late IS-lock arrives for an
already-confirmed transaction, which re-emits
`TransactionInstantSendLocked`. Capture that observable behavior so any
future change to the record-context downgrade or event-suppression rules
shows up as a deterministic test failure rather than a silent semantic
drift.

Addresses manki review on PR #116
#116 (comment)
If a test waits on `block_process_change_record_count` and then immediately reads `block_process_change_count`, the latter could read zero because it was incremented after the per-record loop. Reordering the increments guarantees that observing any record implies the per-callback counter is already bumped.
@manki-review
Copy link
Copy Markdown

manki-review Bot commented Apr 27, 2026

Manki — Review complete

Planner (25s)
    refactor · 2125 lines · 4 agents
    review effort: medium · judge effort: high

Review — 23 findings
    ✅ Correctness & Logic — 5 (345s)
    ✅ Testing & Coverage — 5 (233s)
    ✅ Architecture & Design — 7 (219s)
    ✅ Dependencies & Integration — 6 (252s)

Judge — 20 kept · 2 dropped (88s)
    kept: 8 warning · 7 suggestion · 5 nitpick
    dropped: 1 warning · 1 nitpick

Review metadata

Config:

  • Models: reviewer=claude-sonnet-4-6, judge=claude-opus-4-7
  • Review level: large (auto, 2125 lines)
  • Team: Correctness & Logic, Testing & Coverage, Architecture & Design, Dependencies & Integration
  • Memory: disabled
  • Nit handling: issues

Judge decisions:

  • ✓ Kept: "Race condition: last_synced_height stored after count increment in on_synced_height_updated" (warning, high confidence) — "Impact: Medium (flaky test failures), Likelihood: Possible (depends on thread scheduling). The fetch_add-then-store ordering means a waiter that observes the count bump can still read a stale height. Fix is trivial — store first."
  • ✓ Kept: "Silent empty account_path delivered to C callback on CString conversion failure" (nitpick, high confidence) — "Impact: High if it triggers, Likelihood: Unlikely (derivation paths never contain NUL bytes). The unwrap_or_default fallback masks an internal logic error rather than catching a realistic input. Worth fixing for defense-in-depth but not a real failure mode."
  • ✓ Kept: "Explicit drop calls after synchronous callback invocation are redundant" (nitpick, high confidence) — "Impact: Low (cosmetic/readability), Likelihood: N/A. Explicit drops are merely redundant, not incorrect. Style preference."
  • ✓ Kept: "Unchecked as u32 cast on ffi_updates.len() silently truncates for pathologically large update counts" (nitpick, high confidence) — "Impact: Critical if triggered, Likelihood: Unlikely (would require billions of records in one block). Practically unreachable on Dash. Same as finding 23 — duplicate."
  • ✓ Kept: "Byte-index slice in short_wallet will panic on non-ASCII wallet IDs" (nitpick, high confidence) — "Impact: Medium (panic in CLI tool), Likelihood: Unlikely (wallet IDs are hex-encoded). Only in a CLI binary, not library code. Cosmetic robustness improvement."
  • ✓ Kept: "IS lock bytes silently dropped in test callback — IS lock data correctness never verified" (suggestion, high confidence) — "Impact: Medium (test gap), Likelihood: Probable (no positive test exists). The IS-lock bytes are consensus-critical data, so end-to-end verification matters. But this is a test-coverage suggestion, not a code defect."
  • ✓ Kept: "block_record_actions.contains(Updated) checks entire accumulated history, not the post-sync delta" (suggestion, medium confidence) — "Impact: Low (test-quality improvement), Likelihood: Unlikely to mask a real regression today. Reasonable scoping refinement."
  • ✓ Kept: "last_unconfirmed never asserted positive immediately after mempool receipt, leaving unconfirmed-balance update untested" (suggestion, medium confidence) — "Impact: Medium (gap in balance-update assertion), Likelihood: Probable for missed regressions. Sensible test improvement."
  • ✓ Kept: "test_ffi_sync_then_generate_blocks has no assertions on FFIRecordAction or block_account_paths" (suggestion, medium confidence) — "Impact: Low (test coverage), Likelihood: Possible. Other tests cover these fields; duplication is optional."
  • ✓ Kept: "wait_for_mempool_tx extended to accept InstantSend context but IS-lock-first mempool path never exercised" (suggestion, medium confidence) — "Impact: Medium (dead arm could regress silently), Likelihood: Possible. Ties to the missing IS-lock positive test."
  • ✓ Kept: "Silent record loss when account_type.derivation_path() fails for any account type" (warning, high confidence) — "Impact: High (silent persistence loss), Likelihood: Possible (any new AccountType with a buggy derivation_path). The wallet event pipeline is the sole delivery path, so a silent drop with only a tracing::warn is risky for downstream consumers."
  • ✓ Kept: "Strict equality on last_synced_height vs dashd.initial_height is fragile" (warning, high confidence) — "Impact: Medium (CI flakes), Likelihood: Possible on slow CI. >= expresses the true invariant for a monotonic max."
  • ✓ Kept: "MempoolTransactionReceived carries a single account_path, silently discarding other affected accounts" (warning, high confidence) — "Impact: High (incomplete per-account ledgers for multi-account txs), Likelihood: Probable (internal transfers, CoinJoin). Multiple reviewers flagged this independently across two passes — strong consensus. Merged with finding 20."
  • ✓ Kept: "Breaking ABI change in repr(C) FFIWalletEventCallbacks without version signal" (warning, high confidence) — "Impact: Critical (memory corruption for stale consumers), Likelihood: Probable without version bump. Multiple reviewers across passes flagged this. Merged with finding 18."
  • ✓ Kept: "FFIBlockRecordUpdate.account_path is a borrowed pointer with no lifetime signal to C consumers" (suggestion, high confidence) — "Impact: High if a consumer caches the struct, Likelihood: Possible (easy misuse pattern). Documented but the type signature invites the bug."
  • ✓ Kept: "Race condition between block_process_change_record_count and block_process_change_count reads in tests" (warning, high confidence) — "Impact: Medium (flaky/unreliable test invariants), Likelihood: Possible during concurrent callback firing. Multiple reviewers flagged this consistently across passes."
  • ✓ Kept: "TransactionInstantSendLocked event has no unit or positive-path integration test" (warning, high confidence) — "Impact: High (silent regression of consensus-relevant path), Likelihood: Probable for future changes. Multiple reviewers flagged this across passes."
  • ✗ Dropped: "Breaking ABI change in repr(C) FFIWalletEventCallbacks struct" (ignore, high confidence) — "Duplicate of finding 14 — same ABI-break concern at slightly different line numbers. Merged."
  • ✓ Kept: "API contract break: account_index (u32) replaced by derivation-path string in OnMempoolTransactionReceivedCallback" (warning, high confidence) — "Impact: Critical (memory corruption for stale consumers reading pointer as u32), Likelihood: Probable without version bump. Tied to the broader ABI-version-signal concern."
  • ✓ Kept: "Semantic ambiguity: 'spendable' renamed to 'confirmed' may silently alter consumer balance semantics" (suggestion, medium confidence) — "Impact: Medium (consumer-visible semantic shift), Likelihood: Possible. Consumer behaviour may quietly diverge for coinbase/locked outputs. Documentation/migration note warranted."
  • ✓ Kept: "record_balance silently no-ops on null balance pointer, masking dispatcher bugs in tests" (nitpick, high confidence) — "Impact: Low (test-only helper, masking dispatcher bugs), Likelihood: Unlikely (dispatcher always passes valid). debug_assert is a cheap addition."
  • ✗ Dropped: "ffi_updates.len() as u32 cast silently truncates if update count overflows u32" (ignore, high confidence) — "Duplicate of finding 4 — same overflow concern. Merged."

Timing:

  • Parse: 1.7s
  • Review agents: 373.8s
  • Judge: 87.6s
  • Total: 463.1s

@manki-review manki-review Bot dismissed their stale review April 27, 2026 07:32

Superseded by new review

Copy link
Copy Markdown

@manki-review manki-review Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most prior threads remain open — the FFI ABI break, multi-account silent drop, and missing IS-lock positive test are still unresolved. New finding worth attention: the synced_height_updated store/fetch_add ordering is reversed, which can cause spurious test failures.

📊 20 findings (8 warning, 7 suggestion, 5 nitpick) · 2125 lines · 463s

Review stats
{
  "model": "claude-sonnet-4-6",
  "reviewTimeMs": 463119,
  "diffLines": 2125,
  "diffAdditions": 1198,
  "diffDeletions": 927,
  "filesReviewed": 16,
  "agents": [
    "Correctness & Logic",
    "Testing & Coverage",
    "Architecture & Design",
    "Dependencies & Integration"
  ],
  "findingsRaw": 23,
  "findingsKept": 20,
  "findingsDropped": 3,
  "severity": {
    "blocker": 0,
    "warning": 8,
    "suggestion": 7,
    "nitpick": 5
  },
  "verdict": "REQUEST_CHANGES",
  "prNumber": 116,
  "commitSha": "a3bfe37685387f00be28496d9f3eacf8ce622d85",
  "agentMetrics": [
    {
      "name": "Correctness & Logic",
      "findingsRaw": 5,
      "findingsKept": 5,
      "responseLength": 4905
    },
    {
      "name": "Testing & Coverage",
      "findingsRaw": 5,
      "findingsKept": 5,
      "responseLength": 5982
    },
    {
      "name": "Architecture & Design",
      "findingsRaw": 7,
      "findingsKept": 7,
      "responseLength": 6649
    },
    {
      "name": "Dependencies & Integration",
      "findingsRaw": 6,
      "findingsKept": 3,
      "responseLength": 5094
    }
  ],
  "judgeMetrics": {
    "confidenceDistribution": {
      "high": 17,
      "medium": 5,
      "low": 0
    },
    "severityChanges": 22,
    "mergedDuplicates": 1,
    "defensiveHardeningCount": 2,
    "verdictReason": "novel_suggestion"
  },
  "fileMetrics": {
    "fileTypes": {
      ".rs": 15,
      ".toml": 1
    },
    "findingsPerFile": {
      "dash-spv-ffi/tests/dashd_sync/callbacks.rs": 4,
      "dash-spv-ffi/src/callbacks.rs": 7,
      "dash-spv-ffi/src/bin/ffi_cli.rs": 1,
      "dash-spv-ffi/tests/dashd_sync/tests_callback.rs": 4,
      "dash-spv-ffi/tests/dashd_sync/tests_transaction.rs": 1,
      "dash-spv/tests/dashd_sync/helpers.rs": 1,
      "key-wallet-manager/src/lib.rs": 1,
      "key-wallet-manager/src/event_tests.rs": 1
    }
  },
  "reviewerModel": "claude-sonnet-4-6",
  "judgeModel": "claude-opus-4-7"
}

Comment thread dash-spv-ffi/tests/dashd_sync/callbacks.rs
Comment thread dash-spv-ffi/tests/dashd_sync/callbacks.rs
Comment thread dash-spv-ffi/tests/dashd_sync/tests_callback.rs
Comment thread dash-spv-ffi/tests/dashd_sync/tests_callback.rs Outdated
Comment thread dash-spv-ffi/tests/dashd_sync/tests_transaction.rs
Comment thread dash-spv-ffi/src/callbacks.rs Outdated
Comment thread dash-spv-ffi/tests/dashd_sync/tests_callback.rs
Comment thread key-wallet-manager/src/event_tests.rs
Comment thread dash-spv-ffi/src/callbacks.rs Outdated
Comment thread dash-spv-ffi/tests/dashd_sync/callbacks.rs
Store `last_synced_height` before bumping `synced_height_updated_count` and
bump `block_process_change_count` after all per-record state is written so
tests waiting on the per-callback counter observe consistent record state.
Update test waits to use the per-callback counter.

Capture IS lock bytes in `last_islock_bytes` so future tests can verify the
payload round-trips through `InstantLock`.

Slice `block_record_actions` and `block_account_paths` by the pre-sync index
so post-sync assertions only check the new delivery window. Assert
`last_unconfirmed > 0` after mempool receipt before the block-process
callback overwrites it. Add `FFIRecordAction` and BIP-32 path assertions to
`test_ffi_sync_then_generate_blocks`. Replace strict equality on
`last_synced_height` with `>=` to tolerate concurrent block production.
Add `dropped_records_missing_path: u32` to `CheckTransactionsResult` and
increment it whenever `AccountType::derivation_path` fails inside
`check_transaction_in_all_wallets`. Callers and tests can assert this stays
at zero to detect silent record loss for any account type whose
`derivation_path` implementation is missing or buggy.
@manki-review
Copy link
Copy Markdown

manki-review Bot commented Apr 27, 2026

Manki — Review complete

Planner (26s)
    refactor · 2204 lines · 4 agents
    review effort: medium · judge effort: high

Review — 17 findings
    ✅ Correctness & Logic — 4 (272s)
    ✅ Testing & Coverage — 5 (324s)
    ✅ Architecture & Design — 4 (144s)
    ✅ Security & Safety — 4 (136s)

Judge — 9 kept · 0 dropped (89s)
    kept: 1 warning · 1 suggestion · 7 nitpick

Review metadata

Config:

  • Models: reviewer=claude-sonnet-4-6, judge=claude-opus-4-7
  • Review level: large (auto, 2204 lines)
  • Team: Correctness & Logic, Testing & Coverage, Architecture & Design, Security & Safety
  • Memory: disabled
  • Nit handling: issues

Judge decisions:

  • ✓ Kept: "Explicit drop calls after callback invocation are redundant" (nitpick, high confidence) — "Impact: Low (cosmetic noise), Likelihood: Certain → nitpick. Rust drops locals at end of scope regardless. Single reviewer; matches original severity."
  • ✓ Kept: "last_islock_bytes captured by tracker but never read or asserted in any test" (suggestion, high confidence) — "Impact: Medium (untested IS-lock payload), Likelihood: Certain in current state → suggestion. Test infrastructure exists but no positive-path test exercises it; correlates with existing open thread PRRT_kwDOQSlaXs59wOA4."
  • ✓ Kept: "TransactionInstantSendLocked omits the updated TransactionRecord, breaking self-contained atomic persistence" (warning, high confidence) — "Impact: High (breaks stated PR design contract — consumers cannot atomically persist IS-lock state changes from a single event), Likelihood: Certain (every IS-lock event) → warning. Architectural inconsistency directly contradicts the PR's stated goal of self-contained atomic events."
  • ✓ Kept: "Simultaneous acquisition of three mutexes in on_block_process_change test callback risks deadlock" (nitpick, medium confidence) — "Impact: Medium (potential deadlock in test code only), Likelihood: Unlikely (single ordering used today) → suggestion. Test-only code; consolidation is a real ergonomic improvement but not a bug today."
  • ✓ Kept: "Redundant explicit drop calls at end of BlockProcessChange dispatch arm" (nitpick, high confidence) — "Duplicate of Finding 1 — same redundant drops in same dispatch function. Merged: same issue flagged by two reviewers reinforces it's worth a quick cleanup, but still cosmetic."
  • ✓ Kept: "Missing null-check on txid pointer before dereference in CLI callback" (nitpick, high confidence) — "Impact: High (UB on null deref), Likelihood: Possible (extern "C" callable from any C consumer; current Rust dispatch always passes valid ptr but FFI contract doesn't enforce it) → warning. Defensive null-check is the standard pattern for FFI callback boundaries."
  • ✓ Kept: "short_wallet slices String at a fixed byte offset that can panic on non-ASCII input" (nitpick, high confidence) — "Impact: Medium (panic in CLI tool), Likelihood: Unlikely (wallet IDs are hex-encoded ASCII by construction) → suggestion. Latent footgun in CLI-only utility code; downgrading from warning since input is guaranteed ASCII at all current callsites."
  • ✓ Kept: "CString::new(...).unwrap_or_default() silently replaces derivation paths containing interior nulls with empty strings" (nitpick, high confidence) — "Impact: Medium (silent data loss, indistinguishable from real empty path), Likelihood: Unlikely (derivation paths are formatted strings without NUL) → suggestion. Matches original severity; observability improvement is a legitimate hardening."
  • ✓ Kept: "Usize cast of C-supplied update_count in test callback is trusted without bounds verification" (nitpick, high confidence) — "Impact: Medium (OOB reads if misused), Likelihood: Unlikely (only Rust dispatch calls this test callback) → suggestion. Test-only hardening; existing null+count guard is reasonable for trusted-caller invariant."

Timing:

  • Parse: 2.3s
  • Review agents: 351.7s
  • Judge: 88.8s
  • Total: 442.9s

Copy link
Copy Markdown

@manki-review manki-review Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most prior thread concerns remain open — the breaking ABI change, MempoolTransactionReceived single-account-path design, and missing IS-lock positive-path test still stand. New findings center on FFI safety hardening in the CLI binary (non-ASCII slice panic, unguarded txid deref) and an architectural inconsistency where TransactionInstantSendLocked breaks the atomic-event contract by omitting the updated TransactionRecord.

📊 9 findings (1 warning, 1 suggestion, 7 nitpick) · 2204 lines · 443s

Review stats
{
  "model": "claude-sonnet-4-6",
  "reviewTimeMs": 442902,
  "diffLines": 2204,
  "diffAdditions": 1274,
  "diffDeletions": 930,
  "filesReviewed": 16,
  "agents": [
    "Correctness & Logic",
    "Testing & Coverage",
    "Architecture & Design",
    "Security & Safety"
  ],
  "findingsRaw": 17,
  "findingsKept": 9,
  "findingsDropped": 8,
  "severity": {
    "blocker": 0,
    "warning": 1,
    "suggestion": 1,
    "nitpick": 7
  },
  "verdict": "REQUEST_CHANGES",
  "prNumber": 116,
  "commitSha": "51bf6e73014170cb5156eb61e6adc4ad3914a251",
  "agentMetrics": [
    {
      "name": "Correctness & Logic",
      "findingsRaw": 4,
      "findingsKept": 1,
      "responseLength": 3338
    },
    {
      "name": "Testing & Coverage",
      "findingsRaw": 5,
      "findingsKept": 1,
      "responseLength": 5038
    },
    {
      "name": "Architecture & Design",
      "findingsRaw": 4,
      "findingsKept": 3,
      "responseLength": 3548
    },
    {
      "name": "Security & Safety",
      "findingsRaw": 4,
      "findingsKept": 4,
      "responseLength": 3667
    }
  ],
  "judgeMetrics": {
    "confidenceDistribution": {
      "high": 8,
      "medium": 1,
      "low": 0
    },
    "severityChanges": 9,
    "mergedDuplicates": 0,
    "defensiveHardeningCount": 5,
    "verdictReason": "novel_suggestion"
  },
  "fileMetrics": {
    "fileTypes": {
      ".rs": 15,
      ".toml": 1
    },
    "findingsPerFile": {
      "dash-spv-ffi/src/callbacks.rs": 4,
      "dash-spv-ffi/tests/dashd_sync/callbacks.rs": 3,
      "dash-spv-ffi/src/bin/ffi_cli.rs": 2
    }
  },
  "reviewerModel": "claude-sonnet-4-6",
  "judgeModel": "claude-opus-4-7"
}

Comment thread dash-spv-ffi/tests/dashd_sync/callbacks.rs
Comment thread dash-spv-ffi/src/callbacks.rs
@manki-review
Copy link
Copy Markdown

manki-review Bot commented Apr 27, 2026

Manki — Review complete

Planner (29s)
    refactor · 2199 lines · 4 agents
    review effort: medium · judge effort: high

Review — 15 findings
    ✅ Correctness & Logic — 3 (431s)
    ✅ Security & Safety — 4 (262s)
    ✅ Testing & Coverage — 4 (192s)
    ✅ Architecture & Design — 4 (150s)

Judge — 7 kept · 0 dropped (98s)
    kept: 2 warning · 2 suggestion · 3 nitpick

Review metadata

Config:

  • Models: reviewer=claude-sonnet-4-6, judge=claude-opus-4-7
  • Review level: large (auto, 2199 lines)
  • Team: Correctness & Logic, Security & Safety, Testing & Coverage, Architecture & Design
  • Memory: disabled
  • Nit handling: issues

Judge decisions:

  • ✓ Kept: "record_balance called after counter increment in on_mempool_transaction_received, enabling stale-balance read in test" (warning, high confidence) — "Impact: Medium (test flakiness producing spurious failures), Likelihood: Possible (SeqCst ordering allows the interleaving but the window is tiny). Same invariant is correctly applied elsewhere in the same file — the inconsistency here is real and tests do read last_unconfirmed immediately after waiting on this counter."
  • ✓ Kept: "Silent usize → u32 truncation for update_count passed to C callback" (nitpick, high confidence) — "Impact: High in theory (silent record loss), Likelihood: Unlikely (wallet-relevant updates per block are in single digits; u32::MAX is unreachable on-chain). Merged findings 3 and 5 — same cast, same dispatch site. A try_into().expect() costs nothing and documents the assumption."
  • ✓ Kept: "TransactionInstantSendLocked callback omits the updated TransactionRecord" (warning, high confidence) — "Impact: High (defeats stated atomic-persistence contract; consumers must do a separate lookup creating a TOCTOU window), Likelihood: Certain (every IS-lock event lacks the record). Merged findings 4, 7, and 9 — three independent reviewers flagged this and the prior-review thread (PRRT_kwDOQSlaXs59we7E) explicitly remains open. Strong consensus signal that warrants the warning severity despite the ABI break needed to fix."
  • ✓ Kept: "txid dereferenced without null guard in CLI IS-lock callback" (nitpick, high confidence) — "Impact: High if triggered (UB on null deref), Likelihood: Unlikely (Rust dispatch guarantees non-null today; CLI demo binary). Defensive null-check is a reasonable hygiene improvement for an extern "C" function but not load-bearing for current callers."
  • ✓ Kept: "block_record_actions slice offset can panic rather than produce a clear assertion failure" (suggestion, high confidence) — "Impact: Low (test diagnostic quality only), Likelihood: Unlikely (would require a counter/vector divergence bug). Test-only ergonomic improvement; reviewer's own framing is that this just produces a less informative panic message."
  • ✓ Kept: "read_balance silently maps a null pointer to a zero FFIBalance" (suggestion, medium confidence) — "Impact: Medium if pattern propagates (silent suppression of delivery failures), Likelihood: Unlikely in this binary (CLI demo only). Reviewer's concern is about the precedent rather than active risk in this PR — reasonable to either assert or log instead of silently zeroing."
  • ✓ Kept: "Explicit drop(ffi_updates); drop(path_cstrs) after callback invocation are unnecessary" (nitpick, high confidence) — "Impact: Low (cosmetic, no behavioral change), Likelihood: Certain (drops always run regardless). Reviewer correctly notes they don't enforce the lifetime invariant the comment describes — purely a readability point."

Timing:

  • Parse: 3.3s
  • Review agents: 462.5s
  • Judge: 98.5s
  • Total: 564.3s

Copy link
Copy Markdown

@manki-review manki-review Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The IS-lock callback still omits the updated TransactionRecord — three of four reviewers independently flagged this as breaking the PR's stated atomic-persistence goal, and the prior-review thread on the same issue is still unaddressed. Two new ordering races between counter increments and per-record state writes in the test tracker are real but narrow; the rest are minor.

📊 7 findings (2 warning, 2 suggestion, 3 nitpick) · 2199 lines · 564s

Review stats
{
  "model": "claude-sonnet-4-6",
  "reviewTimeMs": 564261,
  "diffLines": 2199,
  "diffAdditions": 1269,
  "diffDeletions": 930,
  "filesReviewed": 16,
  "agents": [
    "Correctness & Logic",
    "Security & Safety",
    "Testing & Coverage",
    "Architecture & Design"
  ],
  "findingsRaw": 15,
  "findingsKept": 7,
  "findingsDropped": 8,
  "severity": {
    "blocker": 0,
    "warning": 2,
    "suggestion": 2,
    "nitpick": 3
  },
  "verdict": "REQUEST_CHANGES",
  "prNumber": 116,
  "commitSha": "a9b17e12ea76fe9bc4f32be38f7434543441a8bc",
  "agentMetrics": [
    {
      "name": "Correctness & Logic",
      "findingsRaw": 3,
      "findingsKept": 2,
      "responseLength": 3209
    },
    {
      "name": "Security & Safety",
      "findingsRaw": 4,
      "findingsKept": 3,
      "responseLength": 4096
    },
    {
      "name": "Testing & Coverage",
      "findingsRaw": 4,
      "findingsKept": 2,
      "responseLength": 5036
    },
    {
      "name": "Architecture & Design",
      "findingsRaw": 4,
      "findingsKept": 3,
      "responseLength": 5181
    }
  ],
  "judgeMetrics": {
    "confidenceDistribution": {
      "high": 6,
      "medium": 1,
      "low": 0
    },
    "severityChanges": 7,
    "mergedDuplicates": 4,
    "verdictReason": "novel_suggestion"
  },
  "fileMetrics": {
    "fileTypes": {
      ".rs": 15,
      ".toml": 1
    },
    "findingsPerFile": {
      "dash-spv-ffi/tests/dashd_sync/callbacks.rs": 1,
      "dash-spv-ffi/src/callbacks.rs": 3,
      "dash-spv-ffi/src/bin/ffi_cli.rs": 2,
      "dash-spv-ffi/tests/dashd_sync/tests_callback.rs": 1
    }
  },
  "reviewerModel": "claude-sonnet-4-6",
  "judgeModel": "claude-opus-4-7"
}

Comment thread dash-spv-ffi/tests/dashd_sync/callbacks.rs
Comment thread dash-spv-ffi/src/callbacks.rs
Comment thread dash-spv-ffi/tests/dashd_sync/tests_callback.rs
Comment thread dash-spv-ffi/src/bin/ffi_cli.rs
Order `record_balance` before `fetch_add` in `on_mempool_transaction_received` so the balance is always stored before the counter increments, matching the pattern in `on_synced_height_updated`.

Add an explicit length `assert!` before slicing `block_record_actions` in `tests_callback.rs` so a counter/vector mismatch produces a clear diagnostic instead of an index-out-of-bounds panic.

Log a `tracing::warn!` in `read_balance` of `ffi_cli` when the balance pointer is null, so the demo CLI no longer silently treats a null as zero.
@manki-review
Copy link
Copy Markdown

manki-review Bot commented Apr 27, 2026

Manki — Review complete

Planner
    2208 lines · 0 agents

Review — 27 findings

Judge — 10 kept · 0 dropped (123s)
    kept: 1 warning · 3 suggestion · 6 nitpick

Review metadata

Config:

  • Models: reviewer=claude-sonnet-4-6, judge=claude-opus-4-7
  • Review level: large (auto, 2208 lines)
  • Team: Security & Safety, Architecture & Design, Correctness & Logic, Testing & Coverage, Dependencies & Integration, Performance & Efficiency, Maintainability & Readability
  • Memory: disabled
  • Nit handling: issues

Judge decisions:

  • ✓ Kept: "short_wallet panics on non-ASCII multi-byte UTF-8 input at byte boundary 8" (nitpick, high confidence) — "Impact: Medium (panic in CLI), Likelihood: Unlikely (wallet_ids are hex-encoded ASCII at all current call sites). The panic cannot trigger with current callers; minor robustness improvement only."
  • ✓ Kept: "record_balance silently drops immature and locked from FFIBalance" (suggestion, medium confidence) — "Impact: Medium (limits future test coverage of coinbase maturation/locked funds), Likelihood: Certain (every record_balance call). Reasonable test-coverage improvement; not currently exercised by tests but blocks future ones."
  • ✓ Kept: "Redundant explicit drop calls after callback invocation in BlockProcessChange dispatch" (nitpick, high confidence) — "Impact: Low (cosmetic/clarity), Likelihood: Certain. Merged findings 4, 7, 14, and 19 — four reviewers flagged the same redundant drops. Compiler already drops in correct LIFO order; explicit drops are noise but harmless."
  • ✓ Kept: "short_wallet() allocates two Strings to produce an 8-character debug prefix" (nitpick, high confidence) — "Impact: Low (CLI diagnostic helper), Likelihood: Certain. Merged findings 5 and 16. truncate(8) would save one allocation, but the helper runs in a non-hot diagnostic path."
  • ✓ Kept: "Unchecked slice index in tests_transaction.rs can produce confusing panic instead of clean assertion failure" (warning, high confidence) — "Impact: Medium (opaque panic vs descriptive failure), Likelihood: Possible (only triggers on counter/vector desync regression). Merged findings 6, 8, 10, and 18 — four independent reviewers flagged this consistency gap with tests_callback.rs which has the assertion. Strong consensus and the fix is trivial."
  • ✓ Kept: "any used instead of all for post-sync mempool account-path assertion" (suggestion, high confidence) — "Impact: Medium (test could pass with malformed paths alongside valid ones), Likelihood: Possible. Tightening to all matches the adjacent block-path assertion and is a strict improvement."
  • ✓ Kept: "on_transaction_instant_send_locked dereferences txid without null check" (nitpick, medium confidence) — "Impact: High (UB on null deref), Likelihood: Unlikely (Rust dispatch always passes valid pointer). FFI hygiene improvement for ABI consumers; other callbacks in this file guard their record pointer."
  • ✓ Kept: "ffi_updates.len() as u32 silently truncates on large update arrays" (nitpick, high confidence) — "Impact: High in theory (size mismatch), Likelihood: Unlikely (block won't have >4B records). debug_assert is cheap defensive hardening but not a real risk."
  • ✓ Kept: "CString::new(...).unwrap_or_default() silently swallows null-byte errors for account paths" (nitpick, medium confidence) — "Impact: High if triggered (silent empty path → wrong account routing), Likelihood: Unlikely (DerivationPath Display does not produce null bytes). Logging on failure is cheap and turns a hard-to-diagnose bug into an observable error."
  • ✓ Kept: "InstantSend lock serialized on every dispatch — allocates Vec in a frequent hot path" (suggestion, medium confidence) — "Impact: Low-Medium (per-IS-lock allocation), Likelihood: Certain on the IS path. Real but minor optimization; requires WalletEvent refactor which is out of scope for this PR."

Timing:

  • Parse: 2.2s
  • Review agents: 281.0s
  • Judge: 122.6s
  • Total: 405.9s

Copy link
Copy Markdown

@manki-review manki-review Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Four reviewers independently flagged the same unguarded slice in tests_transaction.rs that tests_callback.rs already guards — the inconsistency is the most actionable finding here. Most other items are stylistic (redundant drops, short_wallet allocations) and the multi-mutex pattern is debatable since the test's atomicity gate is the counter, not the locks.

📊 10 findings (1 warning, 3 suggestion, 6 nitpick) · 2208 lines · 406s

Review stats
{
  "model": "claude-sonnet-4-6",
  "reviewTimeMs": 405861,
  "diffLines": 2208,
  "diffAdditions": 1278,
  "diffDeletions": 930,
  "filesReviewed": 16,
  "agents": [
    "Security & Safety",
    "Architecture & Design",
    "Correctness & Logic",
    "Testing & Coverage",
    "Dependencies & Integration",
    "Performance & Efficiency",
    "Maintainability & Readability"
  ],
  "findingsRaw": 27,
  "findingsKept": 10,
  "findingsDropped": 17,
  "severity": {
    "blocker": 0,
    "warning": 1,
    "suggestion": 3,
    "nitpick": 6
  },
  "verdict": "REQUEST_CHANGES",
  "prNumber": 116,
  "commitSha": "3d2275458f41f2f3e313a096e2d99276ad32210f",
  "agentMetrics": [
    {
      "name": "Security & Safety",
      "findingsRaw": 3,
      "findingsKept": 1,
      "responseLength": 2861
    },
    {
      "name": "Architecture & Design",
      "findingsRaw": 4,
      "findingsKept": 2,
      "responseLength": 4700
    },
    {
      "name": "Correctness & Logic",
      "findingsRaw": 4,
      "findingsKept": 2,
      "responseLength": 4120
    },
    {
      "name": "Testing & Coverage",
      "findingsRaw": 3,
      "findingsKept": 2,
      "responseLength": 3389
    },
    {
      "name": "Dependencies & Integration",
      "findingsRaw": 3,
      "findingsKept": 3,
      "responseLength": 3205
    },
    {
      "name": "Performance & Efficiency",
      "findingsRaw": 5,
      "findingsKept": 4,
      "responseLength": 4955
    },
    {
      "name": "Maintainability & Readability",
      "findingsRaw": 5,
      "findingsKept": 1,
      "responseLength": 5552
    }
  ],
  "judgeMetrics": {
    "confidenceDistribution": {
      "high": 6,
      "medium": 4,
      "low": 0
    },
    "severityChanges": 10,
    "mergedDuplicates": 11,
    "defensiveHardeningCount": 2,
    "verdictReason": "novel_suggestion"
  },
  "fileMetrics": {
    "fileTypes": {
      ".rs": 15,
      ".toml": 1
    },
    "findingsPerFile": {
      "dash-spv-ffi/src/bin/ffi_cli.rs": 3,
      "dash-spv-ffi/tests/dashd_sync/callbacks.rs": 1,
      "dash-spv-ffi/src/callbacks.rs": 4,
      "dash-spv-ffi/tests/dashd_sync/tests_transaction.rs": 1,
      "dash-spv-ffi/tests/dashd_sync/tests_callback.rs": 1
    }
  },
  "reviewerModel": "claude-sonnet-4-6",
  "judgeModel": "claude-opus-4-7"
}

Comment thread dash-spv-ffi/tests/dashd_sync/callbacks.rs
Comment thread dash-spv-ffi/tests/dashd_sync/tests_transaction.rs
Comment thread dash-spv-ffi/tests/dashd_sync/tests_callback.rs
Comment thread dash-spv-ffi/src/callbacks.rs
Add explicit bounds `assert!` before slicing `block_record_actions` and `block_account_paths` by `block_records_before` in `tests_transaction.rs`, matching the diagnostic style used in `tests_callback.rs`.

Change the mempool BIP-32 path check from `any` to `all` so it matches the strictness of the post-sync block path assertion below it.
@github-actions
Copy link
Copy Markdown

This PR has merge conflicts with the base branch. Please rebase or merge the base branch into your branch to resolve them.

…t ids

Rename and reshape the atomic wallet events surfaced in #116 based on user
feedback:

- `MempoolTransactionReceived` → `TransactionReceived` to drop the misleading
  channel-specific name (the variant also fires for first-seen IS-locked
  transactions, where the record's `context` is `InstantSend(_)` and the
  balance reports as `confirmed`).
- `BlockProcessChange` → `BlockProcessed` for a cleaner reading.
- Replace `BlockRecordUpdate` with a single `RecordChange { account_type,
  action, record }` reused by both `TransactionReceived` (single value) and
  `BlockProcessed` (vec). On the receive path `action` is always `Inserted`,
  but the symmetric type lets consumers share one record-handling code path.

Carry `AccountType` directly through `CheckTransactionsResult` and the
events instead of the previously-derived `DerivationPath`. The path was
deterministic from `(AccountType, Network)` anyway, so the consumer can
reconstruct it on demand. This eliminates the conversion failure mode and
removes the `dropped_records_missing_path` counter and its tracking.

On the FFI side, replace the borrowed BIP-32 path C-string in
`FFIBlockRecordUpdate` with a typed `FFIAccountType` discriminant plus
`account_index: i32` (`-1` for variants without an index) on the new
`FFIRecordChange`. This drops a `CString` allocation per record, gives
consumers a `switch`-able enum that mirrors `AccountType` exactly, and
keeps the path recoverable from `(account_type, account_index, network)`
when it is needed.

Tests updated to match: `event_tests.rs` asserts on `account_type`
discriminants, the FFI integration tracker captures
`FFIAccountType`/`account_index` per callback and asserts the BIP-44
account 0 reaches the boundary intact.
Records now carry their owning `AccountType` directly via a new `account_type` field on `TransactionRecord`, eliminating the external `(AccountType, TransactionRecord)` pairs previously used in `TransactionCheckResult.new_records`/`updated_records` and `CheckTransactionsResult.per_wallet_new_records`/`per_wallet_updated_records`. Producers in `wallet_checker.rs` and `record_transaction` populate the field at creation time using `ManagedAccountType::to_account_type()`. Consumers in `key-wallet-manager/src/process_block.rs` read it back when constructing `RecordChange`.

The on-disk format is unchanged because the field is transient: it is `#[serde(skip)]` with a sentinel default, and the parent `ManagedCoreAccount`'s `Deserialize` impl walks `transactions.values_mut()` after decoding to overwrite each record's `account_type` with its own. `TransactionRecord` has no bincode derive, so no further bincode handling is required.
Renames the wrapper struct emitted by `WalletEvent::TransactionReceived` and `WalletEvent::BlockProcessed` from `RecordChange` to `TransactionRecordUpdate`, and renames the corresponding event fields from `change` to `update` and `changes` to `updates` so the type and the field name both convey "a record paired with the action that just happened to it".

Drops the wrapper's `account_type` field since `TransactionRecord` now carries its own `account_type` (from the prior commit), so the wrapper is just `{ action, record }`. Consumers that previously read `change.account_type` now read `update.record.account_type`.

The FFI side in `dash-spv-ffi/src/callbacks.rs` keeps the existing `FFIRecordChange` struct name for now (the larger FFI consolidation lands in a follow-up), and renames its constructor to `FFIRecordChange::from_record_update` to take `&TransactionRecordUpdate`. The dispatch arms and FFI-facing doc comments are updated to match the new field names (`update` / `updates`, `update_count`).
Delete the duplicate `FFIAccountType` introduced in `dash-spv-ffi/src/callbacks.rs` and reuse the canonical `key_wallet_ffi::types::FFIAccountType`. The two enums had different discriminants for seven variants and shipping both in the public C API was a real ABI hazard.

Move `FFIRecordAction` and the renamed `FFITransactionRecordUpdate` (formerly `FFIRecordChange`) from `dash-spv-ffi/src/callbacks.rs` into `key-wallet-ffi/src/types.rs`, alongside `FFIBalance` and `FFIAccountType`. The wallet-event callback function pointers (`OnTransactionReceivedCallback`, `OnWalletBlockProcessedCallback`) stay in `dash-spv-ffi` and reference the moved types via `key_wallet_ffi::types::*`. `dash-spv-ffi/src/lib.rs` re-exports them so consumers can keep writing `dash_spv_ffi::FFIAccountType`.

Extend `FFITransactionRecord` to carry the owning account on each record. New fields: `account_type: FFIAccountType`, `account_index: u32`, `account_index_secondary: i32` (`-1` sentinel mirroring the existing `FFIAccountType::from_account_type` tuple contract), plus `account_identity_user`/`account_identity_friend: *const [u8; 32]` and `account_key_class: i32` to preserve the Dashpay identity ids and `PlatformPayment` `key_class` that the previous lossy mapping silently dropped at the FFI boundary. The identity-id pointers are heap-allocated on conversion and freed in `Drop`, so they have stable lifetimes both for borrowed-during-callback dispatch and for the owned-by-caller `managed_core_account_get_transactions` path.

Drop the now-redundant `account_type`/`account_index` fields on `FFITransactionRecordUpdate`. The wrapper degenerates to `{ action, record }`, matching the Rust-side `TransactionRecordUpdate { action, record }`. Callback signatures lose their per-update account params and read them from `update.record` instead.
@xdustinface xdustinface force-pushed the v0.42-dev branch 2 times, most recently from 66e5614 to 05b14a8 Compare May 8, 2026 12:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants