Skip to main content

PoolProjection

Struct PoolProjection 

Source
pub struct PoolProjection { /* private fields */ }
Expand description

An event-fed PoolView: the serve gates read {owner, remaining} from an in-memory projection the settlement watcher folds from the PaymentPool event log, so no serve request costs a getPool eth_call.

The map is published through an ArcSwap so a read (status at admission, cached_status at every voucher boundary) is a single atomic load — no lock, no read-modify-write — and never contends with a concurrent reader on the serve hot path. The settlement watcher’s sink is the projection’s single writer and each fold clones-then-publishes the whole map; writes are rare (one per PaymentPool event) next to the per-voucher-boundary reads.

Cheaply cloneable (an Arc around the cell). The settlement watcher’s sink holds one clone to WRITE the projection; the serve handler holds another as an Arc<dyn PoolView> to READ it, both sharing the one ArcSwap.

Implementations§

Source§

impl PoolProjection

Source

pub fn new() -> Self

A fresh, empty projection.

Source

pub fn record_opened(&self, pool_id: B256, owner: Address, deposit: U256)

Apply a PoolOpened(poolId, owner, deposit): record the owner and the initial deposit. Absolute, so a re-delivered log is idempotent. The deposit is a uint256 on the wire but a uint64 on-chain; an out-of-range value (impossible by construction) saturates to u64::MAX, which over-states remaining and so fails toward serving — the fail-open direction.

Source

pub fn record_topup(&self, pool_id: B256, new_deposit: U256)

Apply a PoolToppedUp(poolId, _, newDeposit): deposit = newDeposit. Absolute, so idempotent. Skipped for a pool never opened in the projection’s scan window — there is no owner to serve, and the serve gate fails open.

Source

pub fn record_redeemed( &self, pool_id: B256, provider: Address, lanes: &[LaneSettled], )

Apply a PoolRedeemed(poolId, provider, lanes) for ANY provider: fold each lane’s positive advance into total_redeemed, matching on-chain totalRedeemed += Σ delta. Monotone and idempotent — a replayed event advances no lane and adds nothing. Skipped for a pool the projection has not opened (a redemption whose PoolOpened predates the scan window): without a deposit there is no remaining to reserve, and the serve gate fails open.

Source

pub fn record_closing(&self, pool_id: B256, deadline: u64)

Apply a PoolCloseInitiated(poolId, _, disputeDeadline): mark the pool Closing. Skipped for a pool the projection has not opened — there is no entry to serve, and reads fail open. A Closing pool keeps its remaining and stays redeemable until deadline; PoolReclaimed later removes it via forget.

Source

pub fn forget(&self, pool_id: B256)

Apply a PoolReclaimed(poolId, ..): the pool is Closed and its remainder refunded. Drop it — a later read returns None and the serve gate fails open, exactly as for a pool the projection has not yet seen. A PoolCloseInitiated is folded by Self::record_closing, which marks the pool Closing but keeps it redeemable until the dispute deadline; forget removes the whole entry, lifecycle included, once the reclaim actually lands.

Source

pub fn record_resolved( &self, pool_id: B256, owner: Address, deposit: U256, lifecycle: Lifecycle, )

Seed a pool the projection has NOT observed through the event log, from a direct getPool snapshot (the lazy cold-start backfill). The settlement watcher anchors at head (ColdStart::Head), so a pool opened before this node’s first-ever boot never appears in the forward-only PoolOpened scan. A node onboarding as a NEW provider to such a pool would then have no owner to verify a presented capability against and could never register the serve lane; the background pool-owner resolver resolves the owner off the serve hot path and folds it here.

Inserts ONLY IF the pool is still absent, so a concurrent event fold — the projection’s authoritative writer — always wins and this never clobbers a live entry. deposit is the pool’s FULL cumulative getPool.deposit (not its remaining), seeded with total_redeemed == 0, exactly as Self::record_opened seeds an event-observed open: the projection then rebuilds total_redeemed from post-seed PoolRedeemed deltas off a zero per-lane baseline, so no redemption is ever double-counted. Redemptions that predate the snapshot are simply not folded, which only OVER-states remaining — the fail-toward-serving direction this module commits to. An out-of-range deposit (impossible by construction) saturates to u64::MAX, the same fail-open direction as Self::record_opened.

Source

pub fn snapshot(&self, pool_id: B256) -> Option<PoolStatus>

A non-blocking snapshot read for callers outside an async trait object.

Source

pub fn signer_spent(&self, pool_id: B256, signer: Address) -> u64

A signer’s total on-chain spent in this pool: Σ of its lane advances across EVERY provider. A signer’s cap is shared across all providers (ADR 003 §Deposit Economics), and on-chain spent for a signer is exactly what it has redeemed at each provider summed. The projection folds this per-signer total in record_redeemed alongside total_redeemed, so this read is an O(1) map lookup — the serve path’s mid-stream signer cap-headroom re-check calls it every voucher-boundary interval per live stream (ADR 003 §Pool solvency), and a scan of the lane map here would cost streams × lanes on a busy pool.

0 for a pool the projection has not folded, or one in which the signer has redeemed nothing — both make the re-check’s held_cap − spent headroom its widest, so the re-check fails toward serving on a projection gap (the admit-time getAuthorization already caught an already-exhausted signer authoritatively; this only bounds drain SINCE admit). The folded total cannot exceed total_redeemed, which is itself bounded by the deposit.

Trait Implementations§

Source§

impl Clone for PoolProjection

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for PoolProjection

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for PoolProjection

Source§

fn default() -> Self

Returns the “default value” for a type. Read more
Source§

impl PoolView for PoolProjection

Source§

fn status<'life0, 'async_trait>( &'life0 self, pool_id: B256, ) -> Pin<Box<dyn Future<Output = Option<PoolStatus>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

The pool’s status, or None if the pool cannot be confirmed. The stream-admission read: a caller here may tolerate a blocking source, so an implementation MAY do slow work. The bare PoolProjection reads from memory and returns None for a pool it has not folded; the production wrapper (crate::payment_settlement::ResolvingPoolView) confirms such a pool with one getPool before returning, so the admit gate refuses a pool it still cannot confirm rather than failing open.
Source§

fn signer_spent_cached<'life0, 'async_trait>( &'life0 self, pool_id: B256, signer: Address, ) -> Pin<Box<dyn Future<Output = Option<u64>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

The mid-stream signer-drain read: a signer’s total on-chain spent across every provider in pool_id, from the event-fed projection ONLY — never a chain call. The serve loop pairs it with the cap the node already holds on the lane to test held_cap − spent headroom at the POOL_RECHECK_INTERVAL cadence, so a signer draining its shared cap at another node mid-stream stops this stream before it over-delivers unredeemable bytes (ADR 003 §Pool solvency). Read more
Source§

fn cached_status<'life0, 'async_trait>( &'life0 self, pool_id: B256, ) -> Pin<Box<dyn Future<Output = Option<PoolStatus>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

A read that MUST NOT block: it returns None rather than doing any slow or remote work. The mid-stream serve re-check calls it at each voucher boundary, where a blocking read would stall delivery. The default delegates to Self::status for in-memory test doubles; PoolProjection serves both from the same in-memory map, so neither ever touches the network.
Source§

fn signer_authorization<'life0, 'async_trait>( &'life0 self, pool_id: B256, signer: Address, ) -> Pin<Box<dyn Future<Output = Option<SignerAuthorization>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

The admit-path signer confirm: the request’s voucher signer on-chain authorization in pool_id (ADR 003 §Capability delegation, §Pool solvency). A signer’s cap is shared across every provider, so a signer that has drawn its full cap at other nodes is uncashable here. Its registered cap and expiry are write-once, so they bound every voucher whatever capability the client presents. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> FutureExt for T

Source§

fn with_context(self, otel_cx: Context) -> WithContext<Self> ⓘ

Attaches the provided Context to this type, returning a WithContext wrapper. Read more
Source§

fn with_current_context(self) -> WithContext<Self> ⓘ

Attaches the current Context to this type, returning a WithContext wrapper. Read more
Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> IntoRequest<T> for T

Source§

fn into_request(self) -> Request<T>

Wrap the input message T in a tonic::Request
Source§

impl<Unshared, Shared> IntoShared<Shared> for Unshared
where Shared: FromUnshared<Unshared>,

Source§

fn into_shared(self) -> Shared

Creates a shared type from an unshared type.
Source§

impl<L> LayerExt<L> for L

Source§

fn named_layer<S>(&self, service: S) -> Layered<<L as Layer<S>>::Service, S>
where L: Layer<S>,

Applies the layer to a service and wraps it in Layered.
Source§

impl<T> MaybeSend for T
where T: Send,

Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more