feat: implement CEP-47 server redirect client/server middleware and re-issuance - #78
feat: implement CEP-47 server redirect client/server middleware and re-issuance#78abhayguptas wants to merge 21 commits into
Conversation
8453650 to
fc04859
Compare
This reverts commit 18e6490.
…leaks
- Remove resubscribeAll() to prevent racing with applesauce-relay's native {resubscribe: Infinity}
- Add isDisconnected flag to prevent zombie pool generations after disconnect()
- Prevent liveness timeouts from incorrectly rebuilding an already-rebuilt pool
- Ensure ping monitor is stopped and not restarted when pool is disconnected
- Add small sleeps in E2E tests to stabilize mock relay restarts
|
The redirect module itself is well-designed and faithfully mirrors the CEP-8 payments pattern. Do not merge as-is — there is one blocker causing the 3 test failures, plus a few spec gaps worth addressing. Details and verified fixes below. 🔴 Blocker — root cause of all 3 test failuresFile: The rewritten message handler checks I verified at runtime what Because every wrapper carries an Knock-on effect:
Fix — discriminate on next: (message: unknown) => {
// req() emits typed wrappers {type:'EOSE'|'EVENT'|...}; raw NostrEvents
// from subscription() have no `type`. Check `type` FIRST so the EVENT
// wrapper's own `id` (subscription id) isn't mistaken for an event id.
if (typeof message === 'object' && message !== null && 'type' in message) {
const msg = message as { type: string; event?: NostrEvent };
if (msg.type === 'EOSE') onEose?.();
else if (msg.type === 'EVENT' && msg.event) onEvent(msg.event);
return;
}
if (Array.isArray(message)) {
if (message[0] === 'EOSE') onEose?.();
else if (message[0] === 'EVENT' && message[2]) onEvent(message[2] as NostrEvent);
return;
}
if (message === 'EOSE') onEose?.();
else if (typeof message === 'object' && message !== null && 'id' in message)
onEvent(message as NostrEvent);
},I applied this locally → 505 tests, 500 pass, 0 fail (was 497/3), typecheck clean, no regressions in the relay-pool race-condition tests. Alternatively, reverting 🟡 Spec gaps vs CEP-471. No CEP-17 fallback when provided
|
…47 spec updates - Fix createSubscription message handler to check 'type' in wrapper before checking 'id' property on raw NostrEvent - Fall back to CEP-17 (kind 10002) discovery when provided configuredRelayUrls are unreachable - Convert redirectCounts Map to bounded LruCache(1000) - Add CEP-41 stream and spec deviation comments
|
looks good, two cheap tests away from merge ✅ Fixes landed correctly
🔴 Two tests I'd ask for before mergeBoth are cheap and directly back claims in the PR description that are currently unexercised:
🟡 Minor / follow-ups (non-blocking)
VerdictFunctionally mergeable today. Adding the two integration tests above would de-risk the headline claims; everything else is fine as tracked follow-ups. |
# Conflicts: # src/relay/applesauce-relay-pool.ts
Replaces the TODO from 923b4bb with the actual 3-line assertion and fixes the composition test name to match what it asserts (gating surfaced, no settlement).
The 4 relay-restart tests are the only latency-sensitive tests in the suite (restart + reconnect windows on 100-150ms poll loops). Both parents of this merge are CI-green, but the merge adds ~7 concurrent test files, which can starve the reconnect loop on 2-core runners past the previous 20-40s budgets. Double the outer timeouts and eventually windows; recovery normally takes <2s, so the budgets only exist to catch hangs.
bun 1.4.0 (released 2026-08-20) deterministically breaks the 4 relay-reconnect/restart tests: subscription delivery never restores after a relay restart (publish retry storm + liveness rebuild loop). Verified locally: same tree passes 513/513 under 1.3.14, fails those 4 under 1.4.0 — at every revision including both CI-green parents of this merge. 'bun-version: latest' made the merge the first post-1.4.0 run. Pin all workflows to 1.3.14 until bun fixes the upstream regression.
bun 1.4.0 (#31518) made CloseEvent semantics spec-correct: completed server-initiated close handshakes now report wasClean=true (previously false). Our mock relay simulated outages with close(1011)/close(1001), relying on the old buggy mapping — under 1.4.0 applesauce-relay's reconnect heuristic (only fires on !wasClean) never triggered, so pre-existing subscriptions were never restored after a restart. Replace outage closes with ServerWebSocket.terminate() (1006 / wasClean=false on both 1.3.x and 1.4.x — verified), which is the faithful simulation of a dead relay regardless of bun's close-code mapping. Restore bun-version: latest. Full suite 513/513 under both bun 1.3.14 and 1.4.0.
Description
This PR implements CEP-47 Server Redirect support for
@contextvm/sdk, adding full end-to-end middleware and re-issuance capabilities. It faithfully follows the mentor's callback-driven middleware design without mutating the stateless initialization lifecycle, and allows the client to transparently handle complex chained redirects with a safety hop cap.Features Included
withServerRedirect):resolveRedirectcallback.-32044MCP error payload with the target pubkey and relays.withClientRedirect):-32044error.NostrClientTransportto the target server.maxRedirectsoption (defaulting to 5 hops).ApplesauceRelayPoolmessage handling.applesauce-relayemits raw events rather than typed wrapper objects in thereq()subscription. Fixed the message handler so that subscriptions correctly fire on events.mock-relay-serverconnection mapping which caused test relay timeouts.withClientRedirectis cleanly hooked up intoNostrMCPProxyalongside payments.Testing
Client -> Server A -> Server B -> responds)Client -> Server A -> Server B -> Server C -> responds)Client -> A -> B -> A -> Error thrown via McpError -32044)All unit tests and E2E integration tests are passing successfully.
Changeset
Included a changeset for the upcoming release.