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}