Skip to content

Fresh-wallet chat-subscription backlog + sendDM resolve outlier (follow-up to #555) #559

Description

@vrogojin

Follow-up to #555 filed after running the M9 diagnostic patch against the trader soak on 2026-06-15.

TL;DR

Diagnostic patch used

Temporary instrumentation in sphere-cli/src/trader/acp-transport.ts (not landed). On every acp.command send:

  • [acp-m9] resolvedPubkey set t+Nms, buffered=K/32 dropCount=0 command=X — fires when sendDM resolves, recording the resolve window and how many backlog DMs were buffered during it.
  • [acp-m9] early-message cap reached t+Nms, ... — fires when earlyMessages.length === EARLY_MESSAGE_CAP (silent-drop path).

Patch lives at the head of AcpDmTransportImpl.handleIncoming and inside the comms.sendDM(...).then((sent) => ...) callback. Easy to re-apply if needed — see #558 for the SDK side.

Data from one full soak (8 ACP DM calls)

Section Caller Command resolve t+ms buffered/32 dropCount
§3 spawn alice SET_STRATEGY 142 4 0
§3 spawn bob SET_STRATEGY 198 3 0
§4 probe alice GET_PORTFOLIO 886 11 0
§4 probe bob GET_PORTFOLIO 147 4 0
§5 pre alice GET_PORTFOLIO 136 12 0
§5 pre bob GET_PORTFOLIO 150 5 0
§6 sell alice CREATE_INTENT 144 14 0
§7 buy bob CREATE_INTENT 145 7 0

(Snapshot dir preserved at /tmp/trader-roundtrip-2967836/ via KEEP=1 for reference. Raw [acp-m9] lines are in each snapshots/*-acp-probe.log and the per-command snapshots.)

What ruled out

  • M9 silent-drop. Cap=32, observed peak=14/32 (44 %). Even if the run-1 failure had been the failing condition, the cap was untouched. Drop this from the active hypothesis list.

What remains open

A) Fresh-wallet backlog anomaly (NEW)

Every fresh CLI process — fresh wallet, fresh #p filter — finds 3–14 NIP-17 gift-wrap events arriving on the chat subscription within ~200 ms of subscribe. The decoded events make it through the receiver's NIP-17 unwrap (otherwise they wouldn't reach handleIncoming), so each successfully decrypted to something targeting this controller's transport pubkey.

For a brand-new pubkey there should be nothing matching on the relay. Possibilities:

  1. Relay filter looseness. The testnet relay may be returning kind=1059 events that don't actually match #p:[fresh_pubkey] — for example, by widening the filter under load, or because the #p index isn't applied before the since window. Cheap to verify with nostr-tool sub --kinds 1059 --p <fresh_pubkey> against the same relay from a clean Node script — should return 0 events; if it returns N, that's the bug.
  2. Tenant or escrow side-channel publishes. Something in the spawned stack (HM-tenant ACK, hello, escrow ping, market-API DMs) is publishing kind-1059 events targeting the fresh controller pubkey before the controller even sends its first DM. Plausible since the controller's pubkey is in trader spawn metadata.
  3. HD-derivation collision. The wallet generates multiple addresses and adds all transport pubkeys to allPubkeys for the chat subscription. If any of these derivations collide with previously-used pubkeys on the relay (BIP-32 path reuse, tenant key derivation overlap, etc.), the filter would match historical events. Less likely but worth checking by counting how many distinct senderPubkeys appear in the buffered messages.

The anomaly doesn't break §4 — the buffer drains cleanly when resolvedPubkey resolves and the sender filter throws out anything that isn't the tenant — but it does mean the buffer is starting each session at 30–40 % full of something, which materially shifts the headroom for the EARLY_MESSAGE_CAP discussion above.

B) The 886 ms resolve outlier

Alice's first §4 ACP probe took 886 ms to resolve sendDM, vs 136–198 ms for every other call in the same soak. All other calls (including bob's same-section probe) clustered at 142–198 ms. Hypotheses:

  1. Nametag resolution cache cold-start. sendDM resolves @tenant_nt → tenant pubkey via NostrTransportProvider.resolveNametagInfo which issues a separate REQ on the same socket. First-ever @tenant resolution on a fresh wallet should cost one extra round-trip; cache hits should be ms-fast. Confirm with a debug log around queryBindingByNametag.
  2. REQ multiplexing contention on the shared socket. The chat subscription opens first and immediately starts streaming the (mystery) backlog. The nametag REQ goes out on the same socket — depending on the relay's REQ-serialization behavior, the nametag response can wait behind the chat backlog stream. The 886 ms outlier correlates with the bigger backlog (11 messages) on the same call.

(B) and (A) interact — if the relay is dumping unexpected backlog on the chat subscription, the nametag REQ on the same socket pays for it. The fix surface is shared.

C) M10b (per-IP rate-limit) — still untested

Strongest remaining hypothesis for the cross-run monotonic creep in #555. Cheapest discriminator: run §4 from a different egress IP after a slow day on the dev box. Hasn't been attempted.

Suggested next actions

  1. Verify (A) with a clean Nostr query — directly query nostr-relay.testnet.unicity.network for kind=1059, #p=<fresh_pubkey> from a clean Node script. Expected: 0 events. Observed during this soak (indirectly): 3–14. If the direct query reproduces, file as a relay-side issue with concrete numbers.
  2. Run the M10b discriminator (different-IP §4 test).
  3. Independent of Nostr relay degradation across consecutive trader-soak runs — retry-loop flooding hypothesis + amplification of #473 #555, consider raising EARLY_MESSAGE_CAP from 32 to ~128 in sphere-cli/src/trader/acp-transport.ts. Risk is low (in-memory buffer growth), upside is robustness against the (A) anomaly should it ever scale up. Not urgent.
  4. PR feat(transport)(sphere-sdk#555): NIP-17 self-wrap opt-out (M8 mitigation) #558 (SDK M8 opt-out): still valuable but no longer load-bearing for Nostr relay degradation across consecutive trader-soak runs — retry-loop flooding hypothesis + amplification of #473 #555. Wait for M10b results before deciding to wire the CLI opt-in.

Related

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions