feat(payments): with_client_payments, the client payment engine, and the bilateral e2e suite - #114
Draft
harsh04044 wants to merge 10 commits into
Draft
feat(payments): with_client_payments, the client payment engine, and the bilateral e2e suite#114harsh04044 wants to merge 10 commits into
with_client_payments, the client payment engine, and the bilateral e2e suite#114harsh04044 wants to merge 10 commits into
Conversation
…authorization store
…am's drop-cleanup
… with payment-required errors
…ation entry point
…t both transport-construction sites
…registration entry point
…and the correlation fixes paid delivery rides on
…ilaterally over MockRelayPool
…try point and correlation fix
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
part of #100
Stacked on #113 (which is stacked on #112 and #111). Diff base
a37dc27. I'll rebase onto main once the parents merge, same as the earlier PRs in this stack.This adds
with_client_payments, the client peer of #113's entry point, and with it Phase A is complete on both sides: the new bilateral suite runs awith_client_paymentsclient against awith_server_paymentsserver in one process and asserts delivery at the client's own channel.contextvm_sdk::payments::with_client_payments(&mut transport, options)registers once, beforestart(), and installs a payment engine into the transport's inbound path (not a wrapping transport: the proxy owns the transport by value, and the consumer channel strips the event ids a wrapper would need). The engine drives both lifecycles:pay_reqand claimed synchronously before the handler chain spawns, apayment_policygate ahead of every wallet action, synthesized-32000errors for declines and server rejections on the original request id, and a synthetic-progress heartbeat carrying the request's original progress-token JSON value (rmcp's watcher is keyed by exact JSON type)-32042answered through anon_payment_requiredcallback with a byte-for-byte retry of the cached original request under a fresh outer event,-32043retried withretry_after * 1.5^nbackoff capped at 10 s, and exactly seven enumerated ways either code can reach the consumer; everything else is handled internallyThe options struct, callback signatures, and entry point freeze on merge as the API the Phase B rails build on. The proxy gets
payment_optionsonProxyConfig(plus a builder), used in bothNostrMCPProxy::newandserve_client_handler, mirroring the gateway from #113. The payments guide (docs/payments.md) covers both lifecycles end to end.The main thing to review: this changes shipped transport behavior
No rs client could ever receive a paid result, so this PR does surgery on 0.2.x client-transport code:
payment_requiredabove all) consumed the entry and the real response was then dropped as "response for unknown request". Now only a response or error response consumes the entry. This is parity, not new behavior: the ts client classifies by JSON-RPC type and only deletes on the response path. I strengthened the older suites' asserts to check client-channel delivery, watched them fail on the unfixed tree, then watched them pass with the fix.config.timeout(30 s default), so a multi-minute payment would lose its entry mid-settlement even with the fix. The engine refreshes it on a cadence ofmin(synthetic_progress_interval, timeout / 2)for the payment's lifetime. The half-timeout bound is not decoration: a naive touch-on-heartbeat design (30 s cadence against the 30 s retention TTL) lost the entry in 40 of 40 simulated trials at the defaults.&self, sosend()'s body moved onto apub(crate)parts struct of cloned handles, the same refactor the server side did forsend_notification. One non-mechanical bit: the one-shot discovery flag becameArc-shared, since a forked copy would re-send discovery tags on every retry. Behavior-preserving: every moved body was diffed against the original, and all prior suites are unchanged at their base counts.Deliberate divergences from ts
start(), afterclose(), second registration), each a hard error before anything is mutated. ts has no guards and callingwithClientPaymentstwice there can double-pay: each wrapper holds its own in-flight dedup set, so both run the pipeline for one offer. Same reasoning as feat(payments): add the server payments registration entry point and gateway payment options #113's server-side guards.retry_after: 0; a byte-identical same-second retry mints the same Nostr event id, which relays and the server's ingestion dedup swallow, so the ts consumer just times out. The floor closes that.Optionwhere ts always wraps, the policy callback takes the request by value, durations areDurationfields, and the cache keys keep numeric and string ids distinct. All wire-invisible.One interop note: gating identity resolves through the correlation entry, never the arriving wire id, so this client works against both server flavors (rs servers answer gating errors with the inner id; ts servers answer with the rewritten event id, already on the upstream pile).
Known quirks (same as ts, documented, not changed)
payment_policyis the gate for that. Treat it as the spending limit.-32042cycles: a failed verification mints a fresh invoice and the callback is asked again each round. The callback is the budget.Note
The ts double-wrap double-pay (plus its silent loss of the context path) should be reported to the ts-sdk, alongside the earlier findings (the server double-registration double charge, the wire id on gating errors, the
ttl: 0grant, the negotiation latch question).