Skip to main content

urma_node_client/
model.rs

1use bitcoin::BlockHash;
2use serde::{Deserialize, Serialize};
3use urma_chain::observation::Chain;
4
5pub const API_VERSION: u32 = 1;
6pub const SCAN_RULES_VERSION: u32 = 1;
7
8/// Acknowledges a local pin operation at its commit point. This is not a
9/// promise of current chain selection, object availability forever, or peer trust.
10#[derive(Clone, Debug, Serialize, Deserialize)]
11#[serde(deny_unknown_fields)]
12pub struct PinReceipt {
13    pub root: bitcoin::Txid,
14    pub pinned: bool,
15}
16
17/// Explicit local cache maintenance. Counts removed child records, not raw
18/// transactions or bytes; unpruned archives return zero without eviction.
19#[derive(Clone, Debug, Serialize, Deserialize)]
20#[serde(deny_unknown_fields)]
21pub struct PruneReceipt {
22    pub records_removed: u64,
23}
24
25/// Transport data, never trusted merely because it came from another node.
26/// The receiver selects the header independently before verifying this proof.
27/// `commit` authenticates the exact prevout through its TXID; this package does
28/// NOT assert the commit's inclusion, ordering, or witness authenticity. Names
29/// must obtain independently anchored commit placement before applying a record.
30#[derive(Clone, Debug, Serialize, Deserialize)]
31#[serde(deny_unknown_fields)]
32pub struct RecordPackage {
33    pub version: u32,
34    pub chain_genesis: BlockHash,
35    pub block: ChainPoint,
36    pub reveal: urma_chain::inclusion::TransactionProof,
37    pub commit: Vec<u8>,
38}
39
40/// Local evidence at one archive generation. Selective admission establishes
41/// membership and the URMA proof, never a full-block scan or interval coverage.
42#[derive(Clone, Debug, Serialize, Deserialize)]
43pub struct RecordReceipt {
44    pub generation: u64,
45    pub block: ChainPoint,
46    pub position: u32,
47    pub txid: bitcoin::Txid,
48    pub wtxid: bitcoin::Wtxid,
49}
50
51#[derive(Clone, Debug, Serialize, Deserialize)]
52pub struct ArchivedRecord {
53    pub receipt: RecordReceipt,
54    pub package: RecordPackage,
55}
56
57/// A successful complete reconstruction, distinct from interval coverage.
58/// Counts describe the current archive snapshot. A reorg can leave all bytes
59/// available locally while some memberships no longer belong to the selected chain.
60#[derive(Clone, Debug, Serialize, Deserialize)]
61pub struct ObjectStatus {
62    pub root: bitcoin::Txid,
63    pub verified_generation: u64,
64    pub payload_bytes: u64,
65    pub members_total: u64,
66    pub members_local: u64,
67    /// Previously verified placements still selected, independent of eviction.
68    pub members_selected: u64,
69    pub pinned: bool,
70}
71
72impl ObjectStatus {
73    pub fn locally_complete(&self) -> bool {
74        self.members_total > 0 && self.members_local == self.members_total
75    }
76
77    pub fn selected_complete(&self) -> bool {
78        self.locally_complete() && self.members_selected == self.members_total
79    }
80}
81
82#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
83pub struct ChainPoint {
84    pub height: u64,
85    pub hash: BlockHash,
86}
87
88#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
89#[serde(rename_all = "kebab-case")]
90pub enum Retention {
91    Pruned,
92    Unpruned,
93}
94
95/// The application start is independent of the chain genesis and header anchor.
96/// There is deliberately no implicit tip-minus-N or checkpoint-based default.
97#[derive(Clone, Debug, Serialize, Deserialize)]
98#[serde(deny_unknown_fields)]
99pub struct ArchiveConfig {
100    pub chain: Chain,
101    pub application_start: ChainPoint,
102    pub retention: Retention,
103}
104
105#[derive(Clone, Debug, Serialize, Deserialize)]
106pub struct ArchiveStatus {
107    pub api_version: u32,
108    pub chain_genesis: BlockHash,
109    pub application_start: ChainPoint,
110    pub retention: Retention,
111    pub generation: u64,
112    pub selected_tip: Option<ChainPoint>,
113    /// Contiguous fully scanned and dependency-resolved prefix from app start.
114    /// This does not assert full consensus validation or local object retention.
115    pub indexed_through: Option<ChainPoint>,
116    pub scanned_blocks: u64,
117    pub unresolved_dependencies: u64,
118    pub stored_transactions: u64,
119    pub scan_rules_version: u32,
120    /// Highest height whose prefix from app start lies in current peer coverage.
121    pub peer_synced_through: Option<ChainPoint>,
122    /// Selected placements a covering peer did not list, found by a later scan.
123    pub peer_omissions: u64,
124    /// Selected records admitted selectively whose block has not been scanned.
125    pub unaudited_records: u64,
126}
127
128/// Historical receipt of one atomic cache maintenance step. Later admissions,
129/// pins and reorgs can change usage. Protected evidence and SQLite/WAL overhead
130/// are outside this metric; disabled enforcement does not imply an empty archive.
131#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
132pub struct CacheReceipt {
133    pub enabled: bool,
134    pub limit_bytes: u64,
135    pub before_bytes: u64,
136    pub remaining_bytes: u64,
137    pub removed_records: u64,
138    pub removed_transactions: u64,
139    pub over_budget: bool,
140}
141
142/// Operator-reported service state. Every `last_*_error` describes the latest
143/// attempt of that worker; `*_at` is when it was recorded, in Unix seconds.
144#[derive(Clone, Debug, Serialize, Deserialize)]
145pub struct ServiceStatus {
146    pub archive: ArchiveStatus,
147    pub last_sync_error: Option<String>,
148    pub last_sync_error_at: Option<u64>,
149    pub last_retention_error: Option<String>,
150    pub last_retention_error_at: Option<u64>,
151    pub last_names_error: Option<String>,
152    pub last_names_error_at: Option<u64>,
153    pub last_dependency_error: Option<String>,
154    pub last_dependency_error_at: Option<u64>,
155    pub last_inventory_error: Option<String>,
156    pub last_inventory_error_at: Option<u64>,
157    pub last_discovery_error: Option<String>,
158    pub last_discovery_error_at: Option<u64>,
159    pub commit_sources: Vec<CommitSourceStatus>,
160    pub cache_limit_bytes: u64,
161    pub last_cache_error: Option<String>,
162    pub last_cache_error_at: Option<u64>,
163    pub last_cache_maintenance: Option<CacheReceipt>,
164    pub overlay: Option<OverlayStatus>,
165    /// Litecoin P2P peers the light client holds now; 0 when offline.
166    #[serde(default)]
167    pub chain_peers: u64,
168    pub retention_configured: bool,
169    pub record_sources_configured: u64,
170    pub capabilities: Vec<String>,
171}
172
173#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
174pub struct CommitSourceStatus {
175    pub label: String,
176    pub last_error: Option<String>,
177    pub last_error_at: Option<u64>,
178}
179
180/// Public overlay facts; never secrets. Counters cover this service run.
181#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
182pub struct OverlayStatus {
183    pub peer_id: String,
184    #[serde(default)]
185    pub listen: Vec<String>,
186    #[serde(default)]
187    pub announce: Vec<String>,
188    pub peer_book: u64,
189    pub connected: u64,
190    pub admitted: u64,
191    pub rejected: u64,
192    pub peers: Vec<OverlayPeerStatus>,
193    pub discovery: Option<DiscoveryStatus>,
194}
195
196/// Peer book and discovery counters. `reached` counts entries whose last dial
197/// succeeded, `reached_ever` entries with any past successful dial; a dial
198/// proves an identity answered, not honesty.
199#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
200pub struct DiscoveryStatus {
201    pub seeds: u64,
202    pub book: u64,
203    pub reached: u64,
204    pub reached_ever: u64,
205    pub gossip: u64,
206    pub last_seed_round_error: Option<String>,
207    pub last_seed_round_error_at: Option<u64>,
208    pub last_gossip_error: Option<String>,
209    pub last_gossip_error_at: Option<u64>,
210}
211
212/// A selected record placement a peer's coverage claimed but did not list.
213#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
214pub struct Omission {
215    pub peer: String,
216    pub block: ChainPoint,
217    pub position: u32,
218    pub txid: bitcoin::Txid,
219    pub generation: u64,
220}
221
222/// How a peer-book entry entered the book. Ordered by precedence: learning
223/// never downgrades an entry, and manual and seed entries are never evicted.
224#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
225#[serde(rename_all = "snake_case")]
226pub enum BookOrigin {
227    Gossip,
228    Seed,
229    Manual,
230}
231
232/// One peer-book entry as served by `GET /v1/overlay/book`: an untrusted
233/// routing hint. Addresses carry no terminal `/p2p`; `peer_id` is set once a
234/// dial has authenticated it. Times are Unix seconds.
235#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
236pub struct BookEntry {
237    pub peer_id: Option<String>,
238    pub addrs: Vec<String>,
239    pub origin: BookOrigin,
240    pub last_success: Option<u64>,
241    pub failures: u32,
242    pub banned_until: Option<u64>,
243}
244
245#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
246pub struct OverlayPeerStatus {
247    pub label: String,
248    pub cursor: u64,
249    pub synced_through: Option<u64>,
250    pub last_error: Option<String>,
251    pub last_error_at: Option<u64>,
252    /// Unix second until which sync skips this peer after a peer error.
253    pub backoff_until: Option<u64>,
254    /// Unix second of this peer's last turn with an admission or coverage.
255    pub last_progress_at: Option<u64>,
256}