Skip to main content

urma_node/
client.rs

1//! Optional, explicitly configured loopback client. No daemon is started and no
2//! DNS, proxy, environment configuration or chain source is consulted.
3//!
4//! Responses are DATA supplied by a locally trusted operator, not independently
5//! verified chain evidence. A receiver using a package from a peer must pass it
6//! through `Engine::admit_record`, with its independently selected chain view.
7//!
8//! Transport, framing limits and operation deadlines are provided by
9//! [`urma_node_client::Client`]. This wrapper preserves Node error types and
10//! the local record-source adapter; it performs no automatic retry.
11
12use crate::{
13    Error,
14    model::{
15        ArchivedRecord, BookEntry, ChainPoint, ObjectStatus, Omission, PinReceipt, PruneReceipt,
16        ServiceStatus,
17    },
18    names::{NamesCursor, NamesPage, NamesResolution, NamesStatus, NamesStep},
19    projection::{WireCursor, WirePage},
20};
21use bitcoin::Txid;
22use std::net::SocketAddr;
23use urma_names::name::Name;
24
25/// An optional local service connection, trusting the configured local operator.
26/// Construction performs no I/O. Each operation opens one bounded connection.
27pub struct Client {
28    inner: urma_node_client::Client,
29}
30
31impl Client {
32    pub fn new(address: SocketAddr, token: String) -> Result<Self, Error> {
33        urma_node_client::Client::new(address, token)
34            .map(|inner| Self { inner })
35            .map_err(Error::from)
36    }
37
38    /// Typed service status reported by the local operator, without verifying its claims.
39    pub fn status(&self) -> Result<ServiceStatus, Error> {
40        self.inner.status().map_err(Error::from)
41    }
42
43    /// Raw status JSON reported by the local operator, without verifying its claims.
44    pub fn status_json(&self) -> Result<serde_json::Value, Error> {
45        self.inner.status_json().map_err(Error::from)
46    }
47
48    /// Read locally archived data. `None` means HTTP 404 (not locally available),
49    /// not proof that a record does not exist. Authentication and other failures
50    /// remain errors. Only the receipt's TXID is compared with the requested ID.
51    ///
52    /// The chain is unknown here: reveal decoding, TXID/witness recomputation,
53    /// inclusion and URMA verification belong to independent admission through
54    /// `Engine::admit_record`. Deserialization does not establish those claims.
55    pub fn record(&self, txid: Txid) -> Result<Option<ArchivedRecord>, Error> {
56        self.inner.record(txid).map_err(Error::from)
57    }
58
59    /// Read object availability reported by the configured local operator.
60    /// These are operator data, not independently verified chain evidence.
61    /// Selected member count is independent of local retention: either count
62    /// may exceed the other, but both must be within a nonzero member total.
63    /// `None` means only that no completed object is recorded locally (HTTP 404),
64    /// not global absence. Root/count sanity checks establish no crypto claims.
65    pub fn object_status(&self, root: Txid) -> Result<Option<ObjectStatus>, Error> {
66        self.inner.object_status(root).map_err(Error::from)
67    }
68
69    /// Request a local pin or unpin and read the operator's acknowledgement at
70    /// its commit point. This is not cryptographic evidence or a promise of
71    /// future availability, chain selection or pin state. Only HTTP 200 with
72    /// the requested root and pin value succeeds. No automatic retry occurs:
73    /// the operation may already have committed before a socket failure.
74    pub fn set_pin(&self, root: Txid, pin: bool) -> Result<PinReceipt, Error> {
75        self.inner.set_pin(root, pin).map_err(Error::from)
76    }
77
78    /// Request explicit local cache pruning and read the operator's removal
79    /// count. This acknowledgement is not cryptographic evidence or a promise
80    /// of future cache contents. Only HTTP 200 succeeds. No automatic retry
81    /// occurs, including when a socket failure leaves the commit outcome unknown.
82    pub fn prune_cache(&self) -> Result<PruneReceipt, Error> {
83        self.inner.prune_cache().map_err(Error::from)
84    }
85
86    /// Recorded peer omissions; HTTP 404 is `Error::Invalid("overlay disabled")`.
87    pub fn overlay_omissions(&self) -> Result<Vec<Omission>, Error> {
88        self.inner.overlay_omissions().map_err(Error::from)
89    }
90
91    /// The peer book's routing hints; HTTP 404 is `Error::Invalid("overlay disabled")`.
92    pub fn overlay_book(&self) -> Result<Vec<BookEntry>, Error> {
93        self.inner.overlay_book().map_err(Error::from)
94    }
95
96    /// Read one bounded page of Wire records from the configured local operator.
97    /// Entry parsing checks data consistency, not authorship or chain inclusion.
98    /// Selected records and scanned coverage remain separate; `next == None`
99    /// ends only the local result set and does not establish chain completeness.
100    /// A snapshot is opaque. HTTP 409 returns `Error::Busy`: discard the partial
101    /// traversal and explicitly restart. No paging, retry or readback is automatic.
102    pub fn wire_page(&self, cursor: Option<&WireCursor>, limit: u32) -> Result<WirePage, Error> {
103        self.inner.wire_page(cursor, limit).map_err(Error::from)
104    }
105
106    /// Register an explicitly located genesis with the local operator. This is
107    /// a local index acknowledgement, not independent evidence or permanent state.
108    /// HTTP 409 remains Busy; a possibly committed operation is never retried.
109    pub fn register_names(&self, genesis: Txid, block: ChainPoint) -> Result<NamesStatus, Error> {
110        self.inner
111            .register_names(genesis, block)
112            .map_err(Error::from)
113    }
114
115    /// Request one local Names step and read operator outcomes. No independent
116    /// proof, automatic stepping, retry or readback; HTTP 409 remains Busy.
117    pub fn names_step(&self, genesis: Txid) -> Result<NamesStep, Error> {
118        self.inner.names_step(genesis).map_err(Error::from)
119    }
120
121    /// Read operator Names progress. None means only locally unregistered (404),
122    /// not global absence. No stepping or retry; HTTP 409 remains Busy.
123    pub fn names_status(&self, genesis: Txid) -> Result<Option<NamesStatus>, Error> {
124        self.inner.names_status(genesis).map_err(Error::from)
125    }
126
127    /// Read local operator resolution for a canonical name, not independent
128    /// ownership evidence or permanent state. No stepping/retry; 409 remains Busy.
129    pub fn names_resolve(&self, genesis: Txid, name: &Name) -> Result<NamesResolution, Error> {
130        self.inner.names_resolve(genesis, name).map_err(Error::from)
131    }
132
133    /// Read one local operator Names page, not independent chain/registry evidence.
134    /// The consumer checks its expected chain and metadata consistency across pages.
135    /// No automatic paging, stepping or retry; 409 remains Busy and ends traversal.
136    /// Absent continuation ends only the local snapshot, not chain completeness.
137    pub fn names_view(
138        &self,
139        genesis: Txid,
140        cursor: Option<&NamesCursor>,
141        limit: u32,
142    ) -> Result<NamesPage, Error> {
143        self.inner
144            .names_view(genesis, cursor, limit)
145            .map_err(Error::from)
146    }
147}
148
149/// Fetch data only from the explicitly configured local operator endpoint.
150/// This adapter performs no peer or network discovery. The receiving engine
151/// independently verifies the package against its own selected headers.
152impl crate::retrieval::RecordSource for Client {
153    fn fetch(&self, txid: Txid) -> Result<Option<crate::model::RecordPackage>, Error> {
154        Ok(self.record(txid)?.map(|record| record.package))
155    }
156}