decdn_client/session.rs
1//! What a paid pull runs under: the deployment it pays on, where it keeps its
2//! local state, how it funds its pool, and how it finds the holders.
3//!
4//! Each value is resolved once by the caller and read by every lane the pull
5//! builds. Nothing here touches the network.
6
7use std::net::SocketAddr;
8use std::path::PathBuf;
9use std::time::Duration;
10
11use alloy::primitives::{Address, U256};
12use decdn_incentive::Deployment;
13use iroh::PublicKey;
14
15/// The chain a pull pays on: its RPC endpoint and the deCDN contracts.
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct ChainConfig {
18 /// The JSON-RPC endpoint every chain read and transaction goes through.
19 pub rpc_url: String,
20 /// The chain id the EIP-712 domains are built for.
21 pub chain_id: u64,
22 /// The `PaymentPool` the pull's pool lives on.
23 pub payment_pool: Address,
24 /// The `SlashJudge` whose domain every probe answer and stream response
25 /// verifies against.
26 pub slash_judge: Address,
27 /// The `CapacityBond` registry discovery reads, and the domain of the
28 /// client identity binding. `None` disables both: the pull needs a pinned
29 /// node, and a node serves it only content it already holds.
30 pub capacity_bond: Option<Address>,
31}
32
33impl ChainConfig {
34 /// The `PaymentPool` deployment this pull runs against: the chain id and
35 /// the contract address. A buyer row is reused only on this deployment.
36 #[must_use]
37 pub const fn deployment(&self) -> Deployment {
38 Deployment {
39 chain_id: self.chain_id,
40 payment_pool: self.payment_pool,
41 }
42 }
43}
44
45/// Where a pull keeps its local state, and where its client sits.
46#[derive(Debug, Clone, PartialEq, Eq)]
47pub struct PullSite {
48 /// The directory of the peer store, and of the scratch files a stream
49 /// keeps while it runs.
50 pub data_dir: PathBuf,
51 /// The client's region, for region-first discovery ordering. `None` skips
52 /// the ordering.
53 pub region: Option<String>,
54 /// The regions a fetch's discovery may probe. Empty allows every region.
55 /// It narrows the candidates and never ranks them, and widens back to
56 /// every region when too few candidates remain. A bundle pull applies it
57 /// to each entry's sample of the run's registry list, with the same
58 /// widening. A list the peer store supplied in place of a failed registry
59 /// read is not narrowed.
60 pub region_allowlist: Vec<decdn_protocol::Region>,
61}
62
63/// Whether a buy may adopt a live pool the wallet already owns on chain when
64/// the buyer store has no row for it.
65///
66/// Adoption assumes the wallet has one buyer: the newest live pool it owns is
67/// taken to be this buyer's own. A wallet shared with another buyer breaks
68/// that, because adopting the other buyer's pool puts a second, independent
69/// voucher series on its lanes.
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub enum ChainAdoption {
72 /// The wallet's pools are this buyer's own, so a lost row is recovered
73 /// from chain rather than escrowing a second deposit.
74 Allowed,
75 /// The wallet belongs to a `decdn-node` daemon, and its newest live pool
76 /// is the one the daemon pays from. The buy opens its own pool instead.
77 Refused,
78}
79
80/// How a self-owned pull funds its pool.
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82pub struct DepositPolicy {
83 /// The deposit a new pool opens at, and the target a reused pool's
84 /// low-water refill and a funding recovery step restore toward. Zero for a
85 /// delegated signer, which owns no pool it could fund.
86 pub working_deposit: U256,
87 /// Approve an unlimited standing USDC allowance instead of the exact
88 /// amount each deposit escrows.
89 pub max_approve: bool,
90 /// Whether a buyer store with no row may adopt the wallet's live pool.
91 pub adoption: ChainAdoption,
92}
93
94/// The proxy-warming rule (ADR 037): route through a nearer non-holder when
95/// the best holder is distant.
96#[derive(Debug, Clone, Copy, PartialEq)]
97pub struct ProxyWarmingParams {
98 /// Whether the rule engages at all.
99 pub enabled: bool,
100 /// The best holder's RTT at or above which a proxy is considered, in ms.
101 pub rtt_threshold_ms: f64,
102 /// How much nearer than the best holder a proxy must be, in ms.
103 pub margin_ms: f64,
104}
105
106impl Default for ProxyWarmingParams {
107 /// The rule on: route through a non-holder at least 30 ms nearer than a
108 /// best holder 150 ms or more away.
109 fn default() -> Self {
110 Self {
111 enabled: true,
112 rtt_threshold_ms: 150.0,
113 margin_ms: 30.0,
114 }
115 }
116}
117
118/// One node every fetch of a pull is pinned to, instead of discovering the
119/// holders.
120#[derive(Debug, Clone, Copy, PartialEq, Eq)]
121pub struct PinnedNode {
122 /// The node's iroh id.
123 pub node_id: PublicKey,
124 /// The node operator's on-chain provider address, which its lane pays.
125 pub provider: Address,
126 /// A direct address to dial the node at, beside its discovery records.
127 pub addr: Option<SocketAddr>,
128}
129
130/// How a pull finds a blob's holders.
131#[derive(Debug, Clone, PartialEq)]
132pub struct DiscoveryOptions {
133 /// The one node to fetch from. `None` discovers the holders.
134 pub pinned: Option<PinnedNode>,
135 /// Read the registry and probe, even when the peer store holds a fresh
136 /// candidate set.
137 pub rediscover: bool,
138 /// How many holders one fetch stripes across at once.
139 pub max_sources: usize,
140 /// The bound on one registry read. The peer-store fallback still runs
141 /// when the read times out.
142 pub registry_timeout: Duration,
143 /// The proxy-warming rule.
144 pub proxy_warming: ProxyWarmingParams,
145}
146
147impl Default for DiscoveryOptions {
148 /// Discover the holders, from the peer store when it holds a fresh
149 /// candidate set; stripe each fetch across up to 4 of them; bound a
150 /// registry read by one hour; and warm proxies by the default rule.
151 fn default() -> Self {
152 Self {
153 pinned: None,
154 rediscover: false,
155 max_sources: 4,
156 registry_timeout: Duration::from_hours(1),
157 proxy_warming: ProxyWarmingParams::default(),
158 }
159 }
160}
161
162/// The points of a fetch's path to its first byte that the pull reports to its
163/// caller, for a time-to-first-byte log. Each is reported every time it is
164/// reached; the caller keeps the first.
165#[doc(hidden)]
166pub trait FetchMarks: Send + Sync {
167 /// Discovery starts a probe round.
168 fn probe_started(&self);
169 /// A lane's pool is open or reused, and its source is ready.
170 fn lane_built(&self);
171 /// A holder joined the running fetch from a probe that answered late.
172 fn holder_joined(&self);
173}