Expand description
Wire message payload types for the cdn/client/v1 paid-delivery protocol.
cdn/client/v1 is the paid byte-transfer path (ADR 005 §cdn/client/v1):
a payer opens a bidirectional QUIC stream, sends a StreamRequest, and
the delivering node answers with a StreamResponse followed by a loop of
ChunkData interleaved with cumulative payment Vouchers, terminating
in ClientMessage::StreamEnd. Acceptance of a voucher is implicit —
delivery simply continues; only rejection is signalled, via
ClientMessage::StreamError. A delivery or payment fault is signalled by
StreamError.
Like crate::message this is a leaf crate with no crypto dependency.
The two signed artifacts on this protocol are produced/verified by
decdn_incentive:
StreamResponse.slash_sig— an EIP-712 secp256k1 signature over the signed body fields{hash, ok, rate_per_mb, total_bytes, pool_id, timestamp_us}(ADR 014 §1), produced by the stream-response slash signer indecdn_incentive(analogous to itsProbeSlashData).erroris unsigned.Voucher.signature— an EIP-712 secp256k1 voucher signature; the wire carries{signature, amount}and the receiver reconstructs the full typed data{poolId, signer, provider, amount, bytesDelivered}from stream context (ADR 005 §Voucher wire format,decdn_incentive::Voucher).
§Signed-field freezing (ADR 013 §Signed Field Freezing)
The signed set on StreamResponseBody is the cdn/client/v1 v1 baseline.
Any change to it is a Tier-3 ALPN bump. New unsigned fields go on
StreamResponse as optional fields, following the total_bytes exemplar
on crate::message::ProbeResponse.
§Variant order
ClientMessage, StreamError, and VoucherRejectReason all have
frozen variant order — postcard encodes each variant as its
declaration-order index. Reordering is a wire-breaking change; the
*_discriminant_is_* / *_variant_order tests lock the assignments.
Structs§
- Chunk
Data - Node → payer chunk of blob bytes. The payload carries at least one byte; its
length is the sender’s choice, bounded above by the framing layer’s
MAX_MESSAGE_SIZE; ADR 005 §cdn/client/v1additionally requires a sender not to cross aCHUNK_BYTESpayment boundary, so in practice a frame runs to at most one interval. Frames need not be uniform, and the frame beforeClientMessage::StreamEndis commonly shorter than the ones before it. - Chunk
Preimage - Payer → node released hash-chain preimage, advancing the lane’s claim by one
chunk with no signature (ADR 003 §Hash-chain metering (
PayWord), ADR 005 §Payment quantum and credit window). - Client
Binding - Off-chain ephemeral client identity binding (ADR 003 §Off-Chain Ephemeral
Binding), carried inside
StreamRequestExt. The address and its attesting signature are paired so neither can appear without the other. - Stream
Request - Payer → node request opening a paid delivery (ADR 005 §
cdn/client/v1). - Stream
Request Ext - Optional
StreamRequestextension fields (ADR 005 §Client identity binding), carried as trailing bytes after theStreamRequestmessage via the two-phase pattern (seeencode_stream_request/parse_stream_request_ext). - Stream
Response - Node → payer response to a
StreamRequest(ADR 005 §cdn/client/v1). - Stream
Response Body - Signed fields of a
StreamResponse(ADR 014 §1). Layout is frozen per ADR 013 — future additions go onStreamResponseas optional unsigned fields, not here. - Stream
Response Ext - Optional
StreamResponseextension fields, carried as trailing bytes after theStreamResponsemessage via the two-phase pattern (ADR 013 §Tier 1; seeencode_stream_response/parse_stream_response_ext). - Voucher
- Payer → node cumulative payment voucher (ADR 005 §Voucher wire format).
- Watermark
Bundle - The node’s true watermark for a pool capability, echoed back on a gated
StreamError::VoucherRejectedso a wallet-less client can self-heal (issue #1481).amount/bytes_deliveredmirror the seller-sidePoolState::last_*fields asu64cumulative totals, for the same reason asVoucher— matching the contract’s on-chainuint64storage.last_signatureis the node’s stored last-accepted client signature (r‖s‖v, exactlyVOUCHER_SIG_LEN) — not a node signature over this bundle — so the client can confirm which of its own vouchers the node holds. - Wire
Capability - A pool owner’s spending capability, carried inside
StreamRequestExt(ADR 003 §Capability delegation).signerandpool_idare not on the wire — they are derived from stream context, exactly like aVoucher’s implicit fields:signeris the request’s bound Ethereum address (ClientBinding::ethereum_address, or the registered on-chain signer whenbindingis absent) andpool_idisStreamRequest::pool_id.
Enums§
- Client
Message - Top-level protocol enum for
cdn/client/v1. Variant order is frozen per ADR 013 — new variants MUST be appended at the end. - Stream
Error - A stream failure code (ADR 005 §Open-time refusal classes).
- Voucher
Reject Reason - Why a
Voucherwas rejected (ADR 005 §VoucherRejectedsemantics).
Constants§
- BINDING_
SIG_ LEN - Exact byte length of a
BindNodeIdclient-binding signature (r‖s‖v, 32+32+1). The same EOA off-chain EIP-712 signing form asSLASH_SIG_LEN/VOUCHER_SIG_LEN(ADR 024 §Off-Chain Signature Verification — EOA Recovery Only); pinned byClientBinding::validate. - CHUNK_
BYTES - The payment quantum: one chunk of delivery, 1 MiB (ADR 003 §Chunk Cadence).
- CHUNK_
FRAME_ HEADERS_ MAX - Stack-buffer size for the full framing +
ChunkDataheader: varintpostcard_len+ the one-byteChunkDatadiscriminant + varintpayload_len. Sized to the unconditionedu32varint width — 5 + 1 + 5 — for the same reason as theChunkDataheader buffer; the cap keeps the bytes actually written to at most 9. - MAX_
CHAIN_ LENGTH - The highest chain index: the hash chain’s incremental range over its anchor (ADR 003 §Chain length and rollover).
- MB_
BYTES - One megabyte in bytes (ADR 003: 1 MB = 1,048,576 bytes, exactly). The unit
of
rate_per_mband, by the identity below, ofCHUNK_BYTES. - NO_
NAMESPACE - The reserved “no namespace”
StreamRequest::namespace_idvalue (all-zero big-endianuint256): content published without a namespace, served best-effort from cache / DHT with no authorized origins (ADR 002 §Namespace 0). - VOUCHER_
SIG_ LEN - Exact byte length of an EOA secp256k1 voucher signature (
r‖s‖v, 32+32+1). MirrorsSLASH_SIG_LEN; both are the EOA off-chain signing form (ADR 024 §Off-Chain Signature Verification — EOA Recovery Only). Carried as aVec<u8>on the wire (serde derives array impls only up to[T; 32]), with the length pinned byVoucher::validate.
Functions§
- encode_
chunk_ frame - Encode one
ClientMessage::ChunkDataframe body directly from its payload. - encode_
chunk_ frame_ headers - Encode the full frame headers for a
ChunkDatapayload ofpayload_lenbytes: the framing varintpostcard_lenfollowed by theChunkDataheader (discriminant + varintpayload_len). The payload itself is not included — the caller sends it asBytesalongside this header via a vectored write. - encode_
stream_ request - Encode a
cdn/client/v1StreamRequestframe payload with an optional trailingStreamRequestExt(ADR 005 §Client identity binding, two-phase). - encode_
stream_ response - Encode a
StreamResponsewith its optional trailingStreamResponseExt(ADR 013 §Tier 1, two-phase). Seeencode_stream_requestfor why the two values are encoded separately. - parse_
stream_ request_ ext - Parse the trailing
StreamRequestExtbytes returned as the remainder bycrate::decode_messageafter aClientMessage::StreamRequest. - parse_
stream_ response_ ext - Parse the trailing
StreamResponseExtbytes returned as the remainder bycrate::decode_messageafter aClientMessage::StreamResponse.