feat(payments): gate priced invocations behind the transparent payment lifecycle - #111
Open
harsh04044 wants to merge 1 commit into
Open
feat(payments): gate priced invocations behind the transparent payment lifecycle#111harsh04044 wants to merge 1 commit into
harsh04044 wants to merge 1 commit into
Conversation
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
Seventh piece of CEP-8, after the targeted sender in #109 and the handshake fix in #110, and the first one where money moves. A priced invocation now triggers
notifications/payment_required, waits for the processor to verify settlement, emitsnotifications/payment_accepted, and only then reaches the MCP handler. A pricing callback can reject (emitsnotifications/payment_rejected, drops) or waive (forwards untouched). Duplicate deliveries of one request event share one payment. And because a payment can outlive the 60 s stale-route sweep, the transport captures the request's routing fields when the invoice goes out and delivers the eventual result from that capture.Nothing registers itself yet: the middleware is exported and wired up only by tests. Registration, explicit gating, and client-side auto-pay are later PRs.
What's here
src/payments/server_payments.rs: options, the factory returningArc<dyn InboundMiddleware>, the pending-payment dedup, the lifecycle.src/payments/server_payments_utils.rs(crate-private): capability matching, PMI selection, timeout arithmetic, resolve-and-initiate. The explicit-gating middleware will share it.payment_notification_sender(snapshot_ttl)returning the notification publish as a closure for the detached middleware, the route-snapshot map, and a route-miss fallback insend_responsethat delivers a paid response after the sweep.InboundContextgains a per-event cancel token (child of the transport's shutdown token), and the drop-cleanup now releases the open-stream writer slot a gated request reserved.tests/payments_transparent_e2e.rs, 7 in-crate.Two deliberate divergences from ts
payment_acceptedpublish abort the forward, which is paid-but-undelivered. Here the failure is logged and the forward happens anyway: the notification is a SHOULD, the result is the point, and the failure is reachable (a paying client is idle by definition and the session LRU evicts idle sessions). The site carries a comment telling future refactors not to tidy it back into an early return.Both look worth reporting upstream as ts defects.
Why it's built this way
tools/callwith a progressToken that never streams takes the deferral's passthrough branch with its slot already deleted, so a fallback beside those arms would never run for it. When both a slot and a snapshot exist the open-stream arm wins and the snapshot is consumed unused. The same snapshot also covers a route popped by a duplicate delivery, a loss path ts shares.send_notification's body moved into a shared static that both the method and the injected sender call, so the two cannot drift on tags or wrap-kind policy.Heads up
Like the targeted sender, the closure captures the discovery tag set when built: build it after setting the announcement tags. Pass the middleware's payment TTL as
snapshot_ttlso snapshots outlive every payment (delivery is session-independent, so this works past the session timeout). And one finding outside this PR's scope: the client transport consumes its response-correlation entry on the first correlated message of any kind, sopayment_requiredmakes our own client drop the acceptance and the paid response. Server-side delivery is correct and asserted on the wire; the fix is recorded for the client payments PR, which without it never sees a paid result.Declared and bounded rather than fixed: the dedup is TTL-bounded while the spec's MUST is unbounded (ts identical; the options doc says so); a verify timeout emits nothing (ts parity, the spec has no signal for it); a waiver's metadata is dropped (ts behavioral parity, against its own docs); a stored priced request can be replayed by a third party after TTL, so the future auto-payer must ignore invoices for requests it has no live pending entry for; oversized priced requests always take the first-processor fallback because our clients put
pmitags on the start frame while identity comes from the end frame; and the snapshot delivery inherits the deferred path's no-fragmentation gap, now reachable for a paid result.