Skip to content

feat: add dual receive for transport v2 (Phase A)#621

Merged
AndreaDiazCorreia merged 3 commits into
mainfrom
feat/transport-v2-phase-a-dual-receive
Jun 18, 2026
Merged

feat: add dual receive for transport v2 (Phase A)#621
AndreaDiazCorreia merged 3 commits into
mainfrom
feat/transport-v2-phase-a-dual-receive

Conversation

@AndreaDiazCorreia

@AndreaDiazCorreia AndreaDiazCorreia commented Jun 18, 2026

Copy link
Copy Markdown
Member

Important

Depends on #620 (transport v2 migration spec). That PR must be merged first.

Implements Phase A — Dual receive of the transport v1 → v2 migration described in docs/architecture/TRANSPORT_V2_MIGRATION.md.

Adds the machinery to receive and decrypt protocol-v2 NIP-44 direct messages (kind 14) alongside the existing v1 gift-wrap path (kind 1059), selecting per node.

Changes

  • Transport enum + resolver (lib/features/mostro/transport.dart): single source of truth for the per-node transport. Phase A always resolves to giftWrap; Phase C wires real protocol_version resolution.
  • Transport-aware order filter (subscription_manager.dart): v2 nodes subscribe to kind 14 pinned to authors=[mostroPubkey] and p=[tradeKeys]; v1 keeps kind 1059.
  • v2 unwrap (nostr_utils.dart): decryptNIP44DirectEvent verifies the kind-14 author signature (author = node), NIP-44 decrypts the content, and returns the message tuple.
  • Receive branch (mostro_service.dart): _onData dispatches on event.kind; v1 and v2 converge on MostroMessage.fromJson(tuple[0]).

Scope

Dormant by design: the resolver returns giftWrap, so the emitted filters and runtime behaviour are identical to today. The v1 gift-wrap path is byte-for-byte unchanged. Phase C activates dual-receive via auto-detection.

Out of scope: dual send (Phase B), protocol_version detection/wiring (Phase C), formal tests (Phase D), NIP-17 peer/dispute chat.

Verification

  • flutter analyze: no issues.
  • flutter test: matches baseline (pre-existing unrelated failures only); v1 path unchanged.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added support for Mostro protocol v2 alongside v1 for wire transport, with automatic per-node detection.
    • Updated orders and trade-key notifications to use protocol-appropriate handling (including v2 direct replies).
    • Improved DM processing for protocol-v2 messages during the migration window.
  • Documentation

    • Refreshed the transport migration architecture guide with clarified phase coverage and behavior.
  • Tests

    • Added unit tests for protocol version parsing and transport resolution.

@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Implements NIP-44 direct (kind-14) wire transport alongside existing gift-wrap (kind-1059). Adds protocol version parsing to MostroInstance, Transport enum with resolver, NIP-44 crypto utilities with event-signature validation, repository streaming for instance updates, transport-aware subscription filters, dual-path decryption in main and background services, and a comprehensive migration specification documenting phases and implementation status.

Changes

Transport V2 Migration

Layer / File(s) Summary
Protocol version parsing and Transport resolution
lib/features/mostro/mostro_instance.dart, lib/features/mostro/transport.dart, test/features/mostro/mostro_instance_test.dart, test/features/mostro/transport_test.dart, docs/architecture/TRANSPORT_V2_MIGRATION.md
MostroInstance gains protocolVersion field (defaulting to 1) from the kind-38385 protocol_version tag; NostrEvent extension adds safe protocolVersion getter. Transport enum defines giftWrap (NIP-59 kind 1059) and nip44 (NIP-44 kind 14). resolveTransport maps version 2 to nip44, 1/null to giftWrap, unknown values degrade to giftWrap with warn-level logging. Tests verify parsing and resolution behavior across edge cases. Migration spec Status section now explicitly marks Phase A (dual receive with transport resolution) as implemented.
Repository broadcasting instance updates
lib/data/repositories/open_orders_repository.dart
Introduces broadcast StreamController for kind-38385 Mostro info events, exposing them via public mostroInstanceStream getter. Repository forwards events through the controller when processing Mostro instance subscriptions and cleans up the controller during disposal.
Transport-aware subscription resolution and filtering
lib/features/subscriptions/subscription_manager.dart
Listens to mostroInstanceStream and re-subscribes when resolved transport changes, tracked by _appliedOrdersTransport. _resolveOrdersTransport derives transport from mostroInstance.protocolVersion with fallback to giftWrap. SubscriptionType.orders filter branches per transport: giftWrap uses kind 1059 with p=tradeKeys; nip44 uses kind 14 with authors=mostroPublicKey and p=tradeKeys.
NIP-44 event decryption and signature validation
lib/shared/utils/nostr_utils.dart
Adds decryptNIP44DirectEvent validating event kind (14), content, private key, expected author match, and event id/signature via _isValidEventSignature helper. _isValidEventSignature recomputes Nostr event id from serialized tuple, SHA-256 hashes it, and verifies Schnorr signature.
Dual-path decryption in MostroService
lib/services/mostro_service.dart
_onData branches on event.kind==14 to use decryptNIP44DirectEvent; otherwise falls back to event.unWrap. Both paths yield unified content string. Message storage key prefers decryptedId, then event.id, then timestamp fallback. Logging reports selected event id and unified content.
Background notification NIP-44 support
lib/features/notifications/services/background_notification_service.dart
Extends _decryptAndProcessEvent kind allowlist to include 14. Refactors _handleTradeKeyEvent to branch on kind: kind-14 loads persisted Mostro pubkey and decrypts with decryptNIP44DirectEvent; other kinds continue unwrap-based decryption. Adds _loadMostroPubkey background helper reading persisted AppSettings from SharedPreferencesAsync.
Migration specification updates
docs/architecture/TRANSPORT_V2_MIGRATION.md
Phase A section emphasizes receive-side detection and per-node transport branching for both main and background paths with persisted pubkey verification. Phase C reframed as send-side wiring only (threading resolved Transport into publishOrder), removing parsing/auto-detection claims. Status section confirms Phase A (dual receive) and transport resolution are implemented; later phases marked pending. Touch points table adds background_notification_service.dart.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~28 minutes

Possibly related PRs

  • MostroP2P/mobile#529: Refactors the same _handleTradeKeyEvent / _decryptAndProcessEvent logic in background_notification_service.dart that this PR extends for kind-14 NIP-44 support.
  • MostroP2P/mobile#620: Directly related — shares the same TRANSPORT_V2_MIGRATION.md NIP-44 migration specification content and protocol_version detection strategy introduced in this PR.
  • MostroP2P/mobile#495: Both PRs modify lib/services/mostro_service.dart's _onData DM/event handling; the retrieved PR removes a dispute-chat guard while this one adds NIP-44 kind-14 decrypt/unwrapping branching.

Suggested reviewers

  • arkanoider
  • grunch
  • Catrya
  • BraCR10

Poem

🐇 Hop, hop through the wire anew,
Kind-14 arrives with a NIP-44 view!
From protocol tags to transports resolved,
The dual-path mystery is halfway solved.
Gift-wrap and direct now dance in two,
The rabbit ships both — the migration's through! 🌿✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add dual receive for transport v2 (Phase A)' directly summarizes the main change—implementing dual-receive support for transport v2 migration in Phase A, which aligns with the PR's primary objective.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/transport-v2-phase-a-dual-receive

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@AndreaDiazCorreia

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 808bd1744f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread lib/services/mostro_service.dart

@ermeme ermeme Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review Summary

Verdict: Changes Requested

Critical

  • The foreground v2 branch is wired, but the background receive path is still incomplete. background_notification_service.dart::_decryptAndProcessEvent only accepts kinds 4 and 1059, and _handleTradeKeyEvent still calls event.unWrap(...). Once the resolver flips to v2, backgrounded Mostro replies will be dropped instead of notifying the user or updating the session.

Looks Good

  • The transport abstraction is cleanly isolated.
  • The foreground decrypt path is structured sensibly for a staged rollout.

Comment thread lib/services/mostro_service.dart

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/architecture/TRANSPORT_V2_MIGRATION.md`:
- Around line 21-22: The status line in the TRANSPORT_V2_MIGRATION.md document
at line 21 incorrectly states that no `lib/` code has been changed yet, but this
branch has already implemented Phase A receive-path code. Update the status text
to accurately reflect the current implementation state of the branch, replacing
the outdated "No `lib/` code has been changed yet" statement with text that
indicates Phase A receive-path code has been implemented to prevent confusion
during rollout and debugging.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8eab4295-22b2-4dd1-a098-5ac4802907e1

📥 Commits

Reviewing files that changed from the base of the PR and between d947795 and 240f8fd.

📒 Files selected for processing (6)
  • docs/architecture/TRANSPORT_V2_MIGRATION.md
  • lib/features/mostro/transport.dart
  • lib/features/notifications/services/background_notification_service.dart
  • lib/features/subscriptions/subscription_manager.dart
  • lib/services/mostro_service.dart
  • lib/shared/utils/nostr_utils.dart

Comment thread docs/architecture/TRANSPORT_V2_MIGRATION.md Outdated

@ermeme ermeme Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review Summary

Verdict: Changes Requested

Blocking issue

  • resolveTransport(null) in subscription_manager.dart is still hardcoded to Transport.giftWrap, so the orders subscription never switches to kind 14 for v2 nodes. That means the new NIP-44 receive path is not actually reachable through the normal subscription flow, which defeats Phase A as described in the migration plan.

Positive

  • The background isolate now handles kind 14, which fixes the earlier gap.
  • The NIP-44 direct decrypt helper is cleanly isolated.

Comment thread lib/features/subscriptions/subscription_manager.dart Outdated
ermeme[bot]
ermeme Bot previously approved these changes Jun 18, 2026

@ermeme ermeme Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Approved. The latest commit wires protocol_version auto-detection, switches orders subscriptions to kind 14 for v2 nodes, and fixes the background receive path as well. I don't see any blocking issues on the current head.

@AndreaDiazCorreia AndreaDiazCorreia dismissed ermeme[bot]’s stale review June 18, 2026 02:58

The merge-base changed after approval.

ermeme[bot]
ermeme Bot previously approved these changes Jun 18, 2026

@ermeme ermeme Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Re-reviewed the current head: protocol_version auto-detection is wired, v2 receive is covered in foreground and background, and the added tests line up with the transport resolver. Approving again on the latest commit.

@AndreaDiazCorreia AndreaDiazCorreia dismissed ermeme[bot]’s stale review June 18, 2026 03:04

The merge-base changed after approval.

@mostronatorcoder mostronatorcoder Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I reviewed the current head and I do see one real blocking issue before merge.

Blocking issue:

  • In MostroService._onData, the storage key for persisted history is now transport-dependent: v1 uses the inner rumor id when available, but v2 falls back to the outer kind-14 event.id because there is no inner rumor.
  • At the same time, MostroMessage.fromJson(result[0]) still gives you the logical Mostro message whose own id is the durable order/message identity used throughout the app.
  • That means the same logical history is no longer keyed consistently across transports. Under v1 you persist under the inner id; under v2 you persist under the transport-level outer event id.

Why this matters:

  • This PR is specifically about dual receive across two envelopes while keeping the logical message layer unchanged.
  • If persisted history keys diverge by transport, replay/dedup/history queries can silently stop talking about the same thing even when the logical Mostro message is the same.
  • In other words, the transport envelope starts leaking into storage identity, which is exactly what this migration should avoid.

What I would want:

  • Persist the received message under the same logical identity across both transports, i.e. the Mostro message/order identity parsed from tuple[0], not a transport-specific outer event id.
  • The outer Nostr event id can still be logged/inspected if useful, but it should not become the durable storage identity for v2 messages.

I think this is a real blocker for a dual-receive phase because otherwise the history model is no longer transport-invariant.

grunch
grunch previously approved these changes Jun 18, 2026

@grunch grunch left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Review — Phase A dual receive

Reviewed the full diff at head 954a7bd1 plus every review thread. Walked the foreground receive path, the background isolate, the subscription/transport resolution, the NIP‑44 decrypt helper and the new tests. flutter analyze is clean on all 7 changed lib/ files and the two new test files pass (27/27).

Earlier review threads — all addressed

  • Background kind‑14 path (codex / ermeme): fixed. _decryptAndProcessEvent now allows kind 14, _handleTradeKeyEvent branches to decryptNIP44DirectEvent, and _loadMostroPubkey reads the node pubkey from persisted AppSettings inside the isolate (no Riverpod there). The orders filter is transferred to the background service via lifecycle_manager, so a v2 node yields a kind‑14 filter there too.
  • resolveTransport(null) hardcoded (ermeme): fixed. _resolveOrdersTransport() now reads the real protocol_version from the kind‑38385 info event, and _initMostroInstanceListener re‑subscribes when the info event arrives after the initial subscription. Covered by transport_test.dart and the protocol_version cases in mostro_instance_test.dart.
  • Doc status line (CodeRabbit): fixed — now states Phase A is implemented.

On the remaining "storage key is transport‑dependent" blocker

I dug into this one and I don't think it holds up — and the suggested remedy would actually break history.

The persisted record key (decryptedId ?? event.id) must be unique per message, because a single order has many messages. Every history query keys on the logical id field of the stored MostroMessage (Filter.equals('id', orderId) in mostro_storage.dart), which is the order id and is fully transport‑invariant. The record key is only a per‑message uniqueness/dedup handle.

So persisting "under the Mostro message/order identity from tuple[0]" would set the record key to the order id, and addMessage's if (await hasItem(id)) return; would then store only the first message per order and silently drop the rest. The current scheme (inner rumor id for v1, outer kind‑14 event id for v2) is the correct design: distinct per‑message keys, with logical grouping done by the id field at query time.

Cross‑transport divergence also can't occur in practice: a node speaks exactly one transport, and within a transport the identifier is stable across relays, so dedup is sound. History identity is already transport‑invariant where it matters (the id field).

Other things I checked and found fine

  • v2 filter pins authors:[mostroPublicKey] + p:[tradeKeys], disambiguating from NIP‑17 peer chat (also kind 14).
  • decryptNIP44DirectEvent verifies kind, expected author, and recomputes the event id + Schnorr signature before decrypting — author signature is load‑bearing for v2, good.
  • event.recipient reads the p tag, so session matching works identically for kind 14.

LGTM. Approving.

@AndreaDiazCorreia AndreaDiazCorreia dismissed grunch’s stale review June 18, 2026 17:40

The merge-base changed after approval.

@AndreaDiazCorreia AndreaDiazCorreia force-pushed the feat/transport-v2-phase-a-dual-receive branch from 954a7bd to d8d09fc Compare June 18, 2026 19:55

@ermeme ermeme Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Re-reviewed the current head. Looks good to me — approve.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib/data/repositories/open_orders_repository.dart (1)

138-144: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reset cached node info when mostroPublicKey changes to avoid stale transport selection.

On a node switch, _mostroInstance keeps the previous node’s info. Downstream transport resolution can therefore use an old protocolVersion until a new info event arrives, causing a wrong orders filter during that window.

Proposed fix
   void updateSettings(Settings settings) {
     if (_settings.mostroPublicKey != settings.mostroPublicKey) {
       logger.i('Mostro instance changed, updating...');
       _settings = settings.copyWith();
+      _mostroInstance = null;
       _events.clear();
       _subscribeToOrders();
     } else {
       _settings = settings.copyWith();
     }
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/data/repositories/open_orders_repository.dart` around lines 138 - 144, In
the updateSettings method within the OpenOrdersRepository class, when the
mostroPublicKey changes and you clear _events and call _subscribeToOrders(),
also reset the _mostroInstance field to null or its default state. This ensures
that stale node information (particularly the old protocolVersion) from the
previous node is not used downstream during transport resolution, preventing
incorrect order filtering until a new node info event arrives.
🧹 Nitpick comments (1)
test/features/mostro/mostro_instance_test.dart (1)

329-357: ⚡ Quick win

Add a regression test for parseable unsupported versions (e.g., protocol_version="3").

This group covers absent/valid/unparseable values, but not the explicit-version-skew path. A single case asserting event.protocolVersion == 3 and MostroInstance.fromEvent(event).protocolVersion == 3 will lock the downgrade contract in resolveTransport(...).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/features/mostro/mostro_instance_test.dart` around lines 329 - 357, Add a
new test case in the protocol version test group that covers a parseable but
unsupported version number. Create a test that builds an event with
protocol_version set to a value like "3" (a number that can be parsed but is not
explicitly handled), then verify that event.protocolVersion correctly parses to
the integer value 3 and that MostroInstance.fromEvent(event).protocolVersion
also equals 3. This test ensures the downgrade contract behavior in
resolveTransport(...) is properly locked in for version skew scenarios.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@lib/data/repositories/open_orders_repository.dart`:
- Around line 138-144: In the updateSettings method within the
OpenOrdersRepository class, when the mostroPublicKey changes and you clear
_events and call _subscribeToOrders(), also reset the _mostroInstance field to
null or its default state. This ensures that stale node information
(particularly the old protocolVersion) from the previous node is not used
downstream during transport resolution, preventing incorrect order filtering
until a new node info event arrives.

---

Nitpick comments:
In `@test/features/mostro/mostro_instance_test.dart`:
- Around line 329-357: Add a new test case in the protocol version test group
that covers a parseable but unsupported version number. Create a test that
builds an event with protocol_version set to a value like "3" (a number that can
be parsed but is not explicitly handled), then verify that event.protocolVersion
correctly parses to the integer value 3 and that
MostroInstance.fromEvent(event).protocolVersion also equals 3. This test ensures
the downgrade contract behavior in resolveTransport(...) is properly locked in
for version skew scenarios.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 08a843b7-6725-4778-ac8f-ee9962be9ecf

📥 Commits

Reviewing files that changed from the base of the PR and between 240f8fd and d8d09fc.

📒 Files selected for processing (10)
  • docs/architecture/TRANSPORT_V2_MIGRATION.md
  • lib/data/repositories/open_orders_repository.dart
  • lib/features/mostro/mostro_instance.dart
  • lib/features/mostro/transport.dart
  • lib/features/notifications/services/background_notification_service.dart
  • lib/features/subscriptions/subscription_manager.dart
  • lib/services/mostro_service.dart
  • lib/shared/utils/nostr_utils.dart
  • test/features/mostro/mostro_instance_test.dart
  • test/features/mostro/transport_test.dart
✅ Files skipped from review due to trivial changes (2)
  • test/features/mostro/transport_test.dart
  • docs/architecture/TRANSPORT_V2_MIGRATION.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • lib/shared/utils/nostr_utils.dart
  • lib/features/notifications/services/background_notification_service.dart
  • lib/services/mostro_service.dart
  • lib/features/subscriptions/subscription_manager.dart

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants