Skip to main content

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}