Skip to main content

PixFillConfig

Struct PixFillConfig 

Source
pub struct PixFillConfig {
    pub enabled: bool,
    pub heartbeat: Duration,
    pub finish_fraction: f64,
    pub loss_margin: f64,
    pub max_rate: u32,
    pub min_surb_reserve: u64,
    pub drain_after_close: bool,
}
Expand description

How the Exit fills the return direction of a funded PIX cycle when the application will not.

§Why the Exit has to send anything at all

A PIX Exit is paid per return packet. Each Exit → Entry packet spends one SURB carrying one share, and a funded cycle only recovers — only unlocks the deposit it has already been paid — once polys × (threshold + surplus) of them have gone out. That figure is 327 680 at the shipped dimensions. Nothing in the protocol makes the application send them: an idle VPN client, a tunnel between keystrokes, or a client that simply reads more than it writes all leave the Exit holding a funded cycle that will never complete. max_recovery_time then closes it, and because the deposit address derives from client and server commitments together, the money is stranded rather than refunded.

So the Exit sends its own keep-alives — “fill” — at whatever rate the remaining emission needs to clear the deadline, minus whatever the application is already providing. The mechanism is deliberately the existing SURB-level keep-alive stream rather than a new message: every shipped Entry already parses it, already counts it as a consumed SURB (so its balancer refills), and already credits it as service against the successor gate.

§What it costs the client

One cycle’s quota per deadline while idle, and nothing at all while the application is busier than the required rate — heartbeat is what fill drops to when organic egress already covers the need. The client accepts this as the price of a Session that completes its cycles; it funds successors exactly as it would have.

§What bounds it

Four separate things, because a rule that sends is the one rule in this module that could be turned against the node running it:

  • max_rate caps the instantaneous rate.
  • min_surb_reserve stops fill from spending the SURBs the Session needs for its own replies, as a ceiling on a reserve derived per Session from the Entry’s announced buffer target.
  • The stall rule holds fill at the heartbeat once the target cycle stops making progress, so a doomed cycle or a share-less SURB supply cannot burn max_rate × max_recovery_time SURBs.
  • The existing deadlines and the service gate are unchanged, and remain the backstop for a client that stops supplying SURBs at all.

Fields§

§enabled: bool

Whether the Exit originates fill keep-alives at all.

Disabling it restores the behaviour of an Exit that only ever answers: funded cycles then complete if and only if the application returns enough traffic, and the ones that do not are closed by max_recovery_time with their deposits stranded. That is a defensible choice only for a deployment whose clients are known to saturate their Sessions.

Default: true.

§heartbeat: Duration

The floor fill rate: one packet per this period, sent even when nothing needs sending.

Fill never drops to silence while a funded cycle is in flight, for two reasons that have nothing to do with the deadline. The Entry’s own idle eviction is refreshed by this traffic, and a Session whose Exit has gone quiet is one whose SURB balancer has no level report to act on. It is also what makes the ramp observable: a rate that is only ever the computed one cannot be told apart from a planner that has stopped planning.

Expressed as a period rather than a rate because it is far below one packet per second and FillRate carries its own unit, so 1 / 60 s is exact rather than a rounded fraction.

Default: 60 s.

§finish_fraction: f64

Fraction of SupervisorConfig::max_recovery_time by which a cycle should be finished.

The planner aims at hard_deadline − (1 − finish_fraction) × max_recovery_time rather than at the deadline itself, so the last quarter of the budget is margin: for the loss the loss_margin does not cover, for a mixnet delay spike, and for the round trip the successor’s commitment and deposit still need after this cycle recovers. Aiming at the deadline exactly would make every cycle a photo finish, and a cycle that misses is worth nothing at all rather than nearly everything.

Default: 0.75.

§loss_margin: f64

Fraction by which the remaining emission is inflated to cover return-path loss.

Fill counts packets it sends; the cycle advances on shares that arrive. The difference is return-path loss, which the Exit cannot observe directly — it sees the reconstructor’s progress and nothing about the packets that produced none. Rather than infer a loss rate from a signal that also moves for a dozen other reasons, the planner simply sends this much extra and lets the ordinary re-planning each second absorb whatever the real figure turns out to be.

Default: 0.05.

§max_rate: u32

Ceiling on the fill rate, in packets per second.

This is the value that decides how much bandwidth an idle Session may cost, and it has to clear the floor a cycle actually needs: the largest quota this node accepts, spread over finish_fraction × max_recovery_time. validate_incoming_session_pix_config enforces exactly that, so a cap set below it is refused at load rather than discovered one stranded deposit at a time. It counts the cap only up to MAX_ASSUMED_SESSION_PACKET_RATE, since fill faster than a Session is assumed to carry finishes nothing sooner.

Default: 250 packets/s, which is ~2.9 Mbps of Session data at full SESSION_MTU segments and about four times the 64 packets/s the shipped defaults require.

§min_surb_reserve: u64

Ceiling on the SURBs that must remain estimated-available before a fill packet is emitted.

Fill is the one egress on the Exit that has no application waiting on it, so it is the one that must yield. Spending the last SURBs on fill is not merely wasteful: a return packet with no SURB to ride holds every packet this node originates for the resolver’s surb_resolution_wait, so an over-eager filler stalls the Session it is trying to save. The SURB-level notification is exempt — it is the signal that asks for more SURBs — but the heartbeat is not, being fill at its floor rather than a different message.

A ceiling rather than the figure used, because the two sides of the comparison are chosen by different nodes: the reserve is measured against the Exit’s estimate of its own SURB buffer for the Session, and how deep that buffer is is the Entry’s decision, announced with its initiation. The effective reserve is therefore never more than a quarter of the Entry’s announced buffer target, floored at one. Without that, an operator sizing this against a production buffer would also be deciding — unintentionally — that every Session whose Entry asks for a shallower one can never be filled at all: the estimate never reaches the reserve, every fill packet is withheld, and the funded cycle strands on max_recovery_time.

Default: 500, matching SurbStoreConfig::distress_threshold. Against the 7 000-SURB balancer target of the worked profile a quarter is 1 750, so the configured value is what binds there and the derivation only engages on a Session whose Entry asked for less than four times it.

§drain_after_close: bool

Whether a Session closed at this Exit keeps draining its buffered SURBs into the funded cycle.

A Session can end at the Exit long before its funded cycle does: the session server closes it, the operator calls SessionManager::close_session, or the peer reports a SessionError. The deposit that cycle was paid is only released once its whole emission has ridden back to the Entry, and the address it sits at derives from both nodes’ commitments — so a cycle abandoned midway strands money both sides have already parted with rather than refunding it. With this on, the Exit answers such a close by keeping the keep-alive stream running until the cycle recovers, spending SURBs it is already holding on shares it has already been paid for.

Deliberately narrow, and inert unless enabled is set — a drain is fill, planned by the same rate law and carried by the same stream, so a disabled planner emits no rate to drain at. Three further conditions bound what it can cost:

  • it is entered only when the Exit’s estimated SURB buffer, net of min_surb_reserve, covers the cycle’s whole remaining emission — a drain that runs out partway spends the reserve and recovers nothing, which is worse than not starting;
  • max_rate and that same reserve bound it exactly as they bound ordinary fill, and the SURB-level notification — fill’s one reserve-exempt packet — is switched off for the duration, since it asks for more SURBs on behalf of a Session that is gone: the Entry itself may well still be there, but nothing is left to act on the request, so no refill answers it;
  • max_recovery_idle and max_recovery_time end it whether or not it succeeds.

Setting it to false restores the immediate teardown on every close path, byte for byte.

Default: true.

Trait Implementations§

Source§

impl Clone for PixFillConfig

Source§

fn clone(&self) -> PixFillConfig

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 PixFillConfig

Source§

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

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

impl Default for PixFillConfig

Source§

fn default() -> Self

Return PixFillConfig { enabled: true, heartbeat: Duration::from_secs(60), finish_fraction: 0.75, loss_margin: 0.05, max_rate: 250, min_surb_reserve: 500, drain_after_close: true }

Source§

impl<'de> Deserialize<'de> for PixFillConfig

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl PartialEq for PixFillConfig

Source§

fn eq(&self, other: &PixFillConfig) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl Serialize for PixFillConfig

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more
Source§

impl StructuralPartialEq for PixFillConfig

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
§

impl<'de, T> BorrowedRpcObject<'de> for T
where T: RpcBorrow<'de> + RpcSend,

§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

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
§

impl<T> Conv for T

§

fn conv<T>(self) -> T
where Self: Into<T>,

Converts self into T using Into<T>. Read more
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

Source§

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

§

impl<T> FmtForward for T

§

fn fmt_binary(self) -> FmtBinary<Self>
where Self: Binary,

Causes self to use its Binary implementation when Debug-formatted.
§

fn fmt_display(self) -> FmtDisplay<Self>
where Self: Display,

Causes self to use its Display implementation when Debug-formatted.
§

fn fmt_lower_exp(self) -> FmtLowerExp<Self>
where Self: LowerExp,

Causes self to use its LowerExp implementation when Debug-formatted.
§

fn fmt_lower_hex(self) -> FmtLowerHex<Self>
where Self: LowerHex,

Causes self to use its LowerHex implementation when Debug-formatted.
§

fn fmt_octal(self) -> FmtOctal<Self>
where Self: Octal,

Causes self to use its Octal implementation when Debug-formatted.
§

fn fmt_pointer(self) -> FmtPointer<Self>
where Self: Pointer,

Causes self to use its Pointer implementation when Debug-formatted.
§

fn fmt_upper_exp(self) -> FmtUpperExp<Self>
where Self: UpperExp,

Causes self to use its UpperExp implementation when Debug-formatted.
§

fn fmt_upper_hex(self) -> FmtUpperHex<Self>
where Self: UpperHex,

Causes self to use its UpperHex implementation when Debug-formatted.
§

fn fmt_list(self) -> FmtList<Self>
where &'a Self: for<'a> IntoIterator,

Formats each item in a sequence. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> FutureExt for T

§

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

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

fn with_current_context(self) -> WithContext<Self>

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

impl<T> Instrument for T

§

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

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

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
§

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

§

fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> R
where Self: Sized,

Pipes by value. This is generally the method you want to use. Read more
§

fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> R
where R: 'a,

Borrows self and passes that borrow into the pipe function. Read more
§

fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> R
where R: 'a,

Mutably borrows self and passes that borrow into the pipe function. Read more
§

fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
where Self: Borrow<B>, B: 'a + ?Sized, R: 'a,

Borrows self, then passes self.borrow() into the pipe function. Read more
§

fn pipe_borrow_mut<'a, B, R>( &'a mut self, func: impl FnOnce(&'a mut B) -> R, ) -> R
where Self: BorrowMut<B>, B: 'a + ?Sized, R: 'a,

Mutably borrows self, then passes self.borrow_mut() into the pipe function. Read more
§

fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
where Self: AsRef<U>, U: 'a + ?Sized, R: 'a,

Borrows self, then passes self.as_ref() into the pipe function.
§

fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> R
where Self: AsMut<U>, U: 'a + ?Sized, R: 'a,

Mutably borrows self, then passes self.as_mut() into the pipe function.
§

fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R
where Self: Deref<Target = T>, T: 'a + ?Sized, R: 'a,

Borrows self, then passes self.deref() into the pipe function.
§

fn pipe_deref_mut<'a, T, R>( &'a mut self, func: impl FnOnce(&'a mut T) -> R, ) -> R
where Self: DerefMut<Target = T> + Deref, T: 'a + ?Sized, R: 'a,

Mutably borrows self, then passes self.deref_mut() into the pipe function.
§

impl<T> Pointable for T

§

const ALIGN: usize

The alignment of pointer.
§

type Init = T

The type for initializers.
§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
§

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

§

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
§

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
§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

§

impl<'de, T> RpcBorrow<'de> for T
where T: Deserialize<'de> + Debug + Send + Sync + Unpin,

§

impl<T> RpcObject for T
where T: RpcSend + RpcRecv,

§

impl<T> RpcRecv for T
where T: DeserializeOwned + Debug + Send + Sync + Unpin + 'static,

§

impl<T> RpcSend for T
where T: Serialize + Clone + Debug + Send + Sync + Unpin,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
§

impl<T> Tap for T

§

fn tap(self, func: impl FnOnce(&Self)) -> Self

Immutable access to a value. Read more
§

fn tap_mut(self, func: impl FnOnce(&mut Self)) -> Self

Mutable access to a value. Read more
§

fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
where Self: Borrow<B>, B: ?Sized,

Immutable access to the Borrow<B> of a value. Read more
§

fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
where Self: BorrowMut<B>, B: ?Sized,

Mutable access to the BorrowMut<B> of a value. Read more
§

fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
where Self: AsRef<R>, R: ?Sized,

Immutable access to the AsRef<R> view of a value. Read more
§

fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
where Self: AsMut<R>, R: ?Sized,

Mutable access to the AsMut<R> view of a value. Read more
§

fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
where Self: Deref<Target = T>, T: ?Sized,

Immutable access to the Deref::Target of a value. Read more
§

fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
where Self: DerefMut<Target = T> + Deref, T: ?Sized,

Mutable access to the Deref::Target of a value. Read more
§

fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self

Calls .tap() only in debug builds, and is erased in release builds.
§

fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self

Calls .tap_mut() only in debug builds, and is erased in release builds.
§

fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
where Self: Borrow<B>, B: ?Sized,

Calls .tap_borrow() only in debug builds, and is erased in release builds.
§

fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Self
where Self: BorrowMut<B>, B: ?Sized,

Calls .tap_borrow_mut() only in debug builds, and is erased in release builds.
§

fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
where Self: AsRef<R>, R: ?Sized,

Calls .tap_ref() only in debug builds, and is erased in release builds.
§

fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Self
where Self: AsMut<R>, R: ?Sized,

Calls .tap_ref_mut() only in debug builds, and is erased in release builds.
§

fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
where Self: Deref<Target = T>, T: ?Sized,

Calls .tap_deref() only in debug builds, and is erased in release builds.
§

fn tap_deref_mut_dbg<T>(self, func: impl FnOnce(&mut T)) -> Self
where Self: DerefMut<Target = T> + Deref, T: ?Sized,

Calls .tap_deref_mut() only in debug builds, and is erased in release builds.
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
§

impl<T> TryConv for T

§

fn try_conv<T>(self) -> Result<T, Self::Error>
where Self: TryInto<T>,

Attempts to convert self into T using TryInto<T>. Read more
Source§

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

Source§

type Error = Infallible

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

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

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.
§

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

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

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
§

fn with_current_subscriber(self) -> WithDispatch<Self>

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