Skip to main content

VoucherRejectReason

Enum VoucherRejectReason 

Source
pub enum VoucherRejectReason {
Show 16 variants BadSignature, WrongSigner, WrongPool, WrongProvider, AmountRegression, BytesRegression, SpendingCapExhausted, CapabilityExpired, PoolExhausted, SignerCapExhausted, BadPreimage, ChainIndexZero, UnanchoredPreimage, ChunkPriceMismatch, Underpaid, UnderFold,
}
Expand description

Why a Voucher was rejected (ADR 005 §VoucherRejected semantics).

The first seven variants, Self::BadPreimage, and Self::UnderFold mirror decdn_incentive::PoolError ∪ VoucherError one-to-one; the handler-side conversion voucher_reject_reason matches those exhaustively so a new PoolError variant fails to compile until this enum is extended (ADR 005 §Mirror obligation). The other variants are emitted directly by the cdn/client/v1 handler: Self::CapabilityExpired fires when the signer’s capability has passed its expiry; Self::PoolExhausted fires when the pool’s remaining deposit can no longer fund further credit; Self::SignerCapExhausted fires when the signer’s shared cap headroom can no longer cover a serve floor; the three remaining hash-chain reasons (Self::ChainIndexZero, Self::UnanchoredPreimage, Self::ChunkPriceMismatch) are raised where the handler holds the per-stream chain anchor or quoted rate a validation enum cannot see; and Self::Underpaid fires when the span a voucher adds over the lane’s accepted watermark pays below the quoted rate_per_mb. Variant order is the postcard wire discriminant, so a reorder is a wire-breaking change.

Variants§

§

BadSignature

Signature malformed (corrupted bytes, non-canonical s, invalid recovery id). VoucherError::InvalidSignature.

§

WrongSigner

Signature well-formed but recovers to the wrong signer. VoucherError::WrongSigner.

§

WrongPool

voucher.pool_id mismatch (also: unknown pool). PoolError::WrongPool.

§

WrongProvider

The voucher names a provider node other than the one receiving it — a capability voucher scoped to one node redeemed against another. PoolError::WrongProvider.

§

AmountRegression

Cumulative amount regressed. PoolError::AmountRegression.

§

BytesRegression

Cumulative bytes delivered regressed. PoolError::BytesRegression.

§

SpendingCapExhausted

The signer’s remaining spending cap is exhausted — the voucher amount exceeds what the capability has left to spend (cap − spent). Recovery: the pool owner raises this signer’s cap or delegates a new capability. PoolError::CapExceeded.

§

CapabilityExpired

The signer’s capability has passed its expiry, or lies within the node’s expiry margin of it. The node’s serve-side check compares its LOCAL wall clock (operator-settable) plus that margin against expiry, refuses the voucher or preimage, and stops serving the lane. The margin is one redeem interval plus a landing slack, so the lane’s claim is final when the margin starts, and the node’s redeem sweep one second later has time to land before the expiry (ADR 003 §Revocation). A lane below the redemption floor can still wait past it. On-chain settlement separately gates redemption on block.timestamp. Already-earned vouchers stay redeemable until expiry at settlement. Recovery: the owner mints a fresh capability with a new expiry — a watermark resync or top-up does not help. Emitted directly by the cdn/client/v1 handler (the node holds the clock), not via the PoolError bridge.

§

PoolExhausted

The pool’s on-chain remaining deposit, minus the refundable floor M and the pool’s already-committed concurrent floor credit, can no longer fund further credit for this stream (ADR 003 §Pool solvency). A pool-wide condition, not this signer’s. Recovery: the pool owner tops up the deposit. Emitted mid-stream by the serve loop after the client has proved capability ownership; the open-time equivalent is StreamError::Unfunded, spoken only to a proven requester. Not watermark-gated.

§

SignerCapExhausted

The signer’s shared on-chain cap − spent headroom, tracked across every provider, can no longer cover a serve floor — the signer has drained its cap at OTHER nodes since this stream was admitted, so further vouchers here would redeem min(desired, cap − spent) ≈ 0 and the node would eat the delivered bytes (ADR 003 §Pool solvency). A per-SIGNER condition, distinct from Self::PoolExhausted: the pool’s remaining can be healthy on other signers’ budgets while this one signer’s cap is spent. Recovery: the pool owner raises this signer’s cap or delegates a fresh capability. Emitted mid-stream after the client has proved capability ownership, so naming the condition is post-auth and leaks nothing an open-time refusal must hide (the admit-time equivalent is StreamError::Unfunded, spoken only to a proven requester). Not watermark-gated.

§

BadPreimage

A released ChunkPreimage does not hash to the stream’s deepest verified preimage in index − verified steps (ADR 003 §Concurrent Streams, Rule 2). A payer bug — a wrong seed, a wrong chain, or a mis-derived index. On-chain the same mismatch reverts BadPreimage, which is caller error and not transient state, so this is terminal and carries no watermark bundle: a preimage has no signature of its own, and a payment watermark cannot repair a hash-chain mismatch.

§

ChainIndexZero

A ChunkPreimage arrived with index == 0, which never travels the wire — index 0 names chain_root and is the settlement case at redemption. A payer bug; do not retry.

Zero is the whole of it. There is no over-large index to report: the wire index is a u8 and MAX_CHAIN_LENGTH is 255, so an index past the end of the chain cannot be encoded. A chain that has run out of indices is not this reason either — the payer rolls to a fresh chain_root first.

§

UnanchoredPreimage

A ChunkPreimage arrived on a stream that holds no chain anchor, so the node cannot name the chain the reveal belongs to (ADR 003 §Concurrent Streams, Rule 1). Per-stream and therefore decidable, which a lane-wide reading would not be. Not fatal: the payer sends the current epoch’s chain_root voucher on this stream and resends the preimage. The resend is free — an at-or-below-watermark voucher is already-satisfied rather than rejected.

§

ChunkPriceMismatch

The voucher’s chunk_price is not the node’s quoted rate_per_mb on a metering voucher, or is non-zero on a sealed one (chain_root == 0).

chunk_price is signed by the payer and a preimage carries no price of its own, so a voucher signed at the governance floor against a node quoting ten times that would meter every later chunk at a tenth of the quote, with no per-tick moment revealing it. The node therefore checks the price it is being paid before it meters against it (ADR 003 §Chunk Cadence). A payer bug: re-read rate_per_mb from the StreamResponse and re-sign; do not retry with the same price.

§

Underpaid

The voucher advances the lane, but the span it adds over the node’s last-accepted watermark pays below the quoted rate_per_mb (ADR 003 §Voucher withholding). The node stops delivery and credits nothing.

An honest payer reaches this only when its local watermark has diverged from the node’s: its cumulative carries bytes the node never accepted a voucher for, so every later span looks short. The reject therefore carries the node’s WatermarkBundle. Recovery: the payer verifies the bundle against its own key, rebases its lane to it once, and retries (ADR 005 §VoucherRejected semantics).

§

UnderFold

The voucher is at or above the signed anchor and names a chain_root other than the live one, which retires the lane’s live chain, but its amount or bytes_delivered does not fold the frontier that chain proved: the signed anchor plus verified_index chunks (ADR 003 §Chain length and rollover). At exactly the anchor, only a voucher that opens a chain qualifies; a sealed one there is Self::AmountRegression. Accepting it would discard chunks the node holds preimages for, so the node adopts nothing.

A payer reaches this when it resumes from a watermark that trails the node’s verified frontier. The reject carries the node’s WatermarkBundle. Recovery: the payer verifies the bundle against its own key, reseeds to it with the chain frontier folded in, and retries. Concurrent streams on the lane get the same bundle; one that finds its ledger already at the bundle retries without a reseed. A voucher that already covers the claim’s amount but falls short on bytes_delivered cannot heal this way — the bundle does not advance the payer’s amount — so the payer surfaces it as terminal. PoolError::UnderFold.

Implementations§

Source§

impl VoucherRejectReason

Source

pub const fn is_watermark_gated(self) -> bool

Whether a StreamError::VoucherRejected carrying this reason is eligible for a WatermarkBundle (issue #1481 §5): exactly five reasons. The three regression/exhaustion reasons are ones a wallet-less client cannot distinguish from chain, since its local watermark is the only thing that could be wrong. Self::Underpaid and Self::UnderFold are the other two: a payer reaches them only through that same watermark divergence, and the bundle states the watermark it has to rebase or fold to. Every other reason (CapabilityExpired, PoolExhausted, SignerCapExhausted, the chain reasons) and the signer/pool/provider mismatches are never eligible — a bundle would not help there, since the fix is not “resync the watermark”.

Single source of truth for the gate: the node checks this before attaching a bundle (crates/node/src/handlers/client/voucher.rs) and the client checks it again before trusting one enough to self-heal (crates/client/src/lib.rs) — both call this rather than each keeping their own copy of the match.

Trait Implementations§

Source§

impl Clone for VoucherRejectReason

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 Copy for VoucherRejectReason

Source§

impl Debug for VoucherRejectReason

Source§

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

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

impl<'de> Deserialize<'de> for VoucherRejectReason

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 Eq for VoucherRejectReason

Source§

impl PartialEq for VoucherRejectReason

Source§

fn eq(&self, other: &Self) -> 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 VoucherRejectReason

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 VoucherRejectReason

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> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

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