Skip to main content

near_kit/types/
block_reference.rs

1//! Block reference types for RPC queries.
2
3use serde::{Deserialize, Serialize};
4
5use super::CryptoHash;
6
7/// Sync checkpoint for block references.
8#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
9#[serde(rename_all = "snake_case")]
10pub enum SyncCheckpoint {
11    /// Genesis block.
12    Genesis,
13    /// Earliest available block.
14    EarliestAvailable,
15}
16
17/// Reference to a specific block for RPC queries.
18///
19/// Every NEAR RPC query operates on state at a specific block.
20#[derive(Clone, Debug, PartialEq, Eq)]
21pub enum BlockReference {
22    /// Query at latest block with specified finality.
23    Finality(Finality),
24    /// Query at specific block height.
25    Height(u64),
26    /// Query at specific block hash.
27    Hash(CryptoHash),
28    /// Query at a sync checkpoint (genesis or earliest available).
29    SyncCheckpoint(SyncCheckpoint),
30}
31
32impl Default for BlockReference {
33    fn default() -> Self {
34        Self::Finality(Finality::Final)
35    }
36}
37
38impl BlockReference {
39    /// Query at final block.
40    pub fn final_() -> Self {
41        Self::Finality(Finality::Final)
42    }
43
44    /// Query at optimistic (latest) block.
45    pub fn optimistic() -> Self {
46        Self::Finality(Finality::Optimistic)
47    }
48
49    /// Query at near-final block.
50    pub fn near_final() -> Self {
51        Self::Finality(Finality::NearFinal)
52    }
53
54    /// Query at specific height.
55    pub fn at_height(height: u64) -> Self {
56        Self::Height(height)
57    }
58
59    /// Query at specific hash.
60    pub fn at_hash(hash: CryptoHash) -> Self {
61        Self::Hash(hash)
62    }
63
64    /// Query at genesis block.
65    pub fn genesis() -> Self {
66        Self::SyncCheckpoint(SyncCheckpoint::Genesis)
67    }
68
69    /// Query at earliest available block.
70    pub fn earliest_available() -> Self {
71        Self::SyncCheckpoint(SyncCheckpoint::EarliestAvailable)
72    }
73
74    /// Convert to JSON for RPC requests.
75    pub fn to_rpc_params(&self) -> serde_json::Value {
76        match self {
77            BlockReference::Finality(f) => {
78                serde_json::json!({ "finality": f.as_str() })
79            }
80            BlockReference::Height(h) => {
81                serde_json::json!({ "block_id": *h })
82            }
83            BlockReference::Hash(h) => {
84                serde_json::json!({ "block_id": h.to_string() })
85            }
86            BlockReference::SyncCheckpoint(cp) => {
87                let cp_str = match cp {
88                    SyncCheckpoint::Genesis => "genesis",
89                    SyncCheckpoint::EarliestAvailable => "earliest_available",
90                };
91                serde_json::json!({ "sync_checkpoint": cp_str })
92            }
93        }
94    }
95}
96
97impl From<Finality> for BlockReference {
98    fn from(f: Finality) -> Self {
99        Self::Finality(f)
100    }
101}
102
103impl From<u64> for BlockReference {
104    fn from(height: u64) -> Self {
105        Self::Height(height)
106    }
107}
108
109impl From<CryptoHash> for BlockReference {
110    fn from(hash: CryptoHash) -> Self {
111        Self::Hash(hash)
112    }
113}
114
115/// Finality level for queries.
116#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
117#[serde(rename_all = "lowercase")]
118pub enum Finality {
119    /// Latest optimistic block. Fastest, but may be reorged.
120    Optimistic,
121    /// Doomslug finality. Irreversible unless validator slashed.
122    #[serde(rename = "near-final")]
123    NearFinal,
124    /// Fully finalized. Slowest, 100% guaranteed.
125    #[default]
126    Final,
127}
128
129impl Finality {
130    /// Get the string representation for RPC.
131    pub fn as_str(&self) -> &'static str {
132        match self {
133            Finality::Optimistic => "optimistic",
134            Finality::NearFinal => "near-final",
135            Finality::Final => "final",
136        }
137    }
138}
139
140/// Transaction execution status for send_tx wait_until parameter.
141#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
142#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
143pub enum TxExecutionStatus {
144    /// Don't wait, return immediately after RPC accepts.
145    None,
146    /// Wait for inclusion in a block.
147    Included,
148    /// Wait for execution (optimistic).
149    #[default]
150    ExecutedOptimistic,
151    /// Wait for inclusion in final block.
152    IncludedFinal,
153    /// Wait for execution in final block.
154    Executed,
155    /// Wait for full finality.
156    Final,
157}
158
159impl TxExecutionStatus {
160    /// Get the string representation for RPC.
161    pub fn as_str(&self) -> &'static str {
162        match self {
163            Self::None => "NONE",
164            Self::Included => "INCLUDED",
165            Self::ExecutedOptimistic => "EXECUTED_OPTIMISTIC",
166            Self::IncludedFinal => "INCLUDED_FINAL",
167            Self::Executed => "EXECUTED",
168            Self::Final => "FINAL",
169        }
170    }
171}
172
173#[cfg(test)]
174mod tests {
175    use super::*;
176
177    #[test]
178    fn test_block_reference_rpc_params() {
179        let final_ref = BlockReference::final_();
180        let params = final_ref.to_rpc_params();
181        assert_eq!(params["finality"], "final");
182
183        let height_ref = BlockReference::Height(12345);
184        let params = height_ref.to_rpc_params();
185        assert_eq!(params["block_id"], 12345);
186    }
187
188    #[test]
189    fn test_finality_as_str() {
190        assert_eq!(Finality::Final.as_str(), "final");
191        assert_eq!(Finality::Optimistic.as_str(), "optimistic");
192        assert_eq!(Finality::NearFinal.as_str(), "near-final");
193    }
194
195    #[test]
196    fn test_tx_execution_status_as_str() {
197        assert_eq!(TxExecutionStatus::Final.as_str(), "FINAL");
198        assert_eq!(
199            TxExecutionStatus::ExecutedOptimistic.as_str(),
200            "EXECUTED_OPTIMISTIC"
201        );
202    }
203
204    #[test]
205    fn test_block_reference_hash_to_rpc_params() {
206        let hash = CryptoHash::hash(b"test block");
207        let block_ref = BlockReference::at_hash(hash);
208        let params = block_ref.to_rpc_params();
209        assert_eq!(params["block_id"], hash.to_string());
210    }
211
212    #[test]
213    fn test_block_reference_constructors() {
214        // Test all constructor methods
215        let final_ref = BlockReference::final_();
216        assert!(matches!(
217            final_ref,
218            BlockReference::Finality(Finality::Final)
219        ));
220
221        let optimistic_ref = BlockReference::optimistic();
222        assert!(matches!(
223            optimistic_ref,
224            BlockReference::Finality(Finality::Optimistic)
225        ));
226
227        let near_final_ref = BlockReference::near_final();
228        assert!(matches!(
229            near_final_ref,
230            BlockReference::Finality(Finality::NearFinal)
231        ));
232
233        let height_ref = BlockReference::at_height(12345);
234        assert!(matches!(height_ref, BlockReference::Height(12345)));
235
236        let hash = CryptoHash::hash(b"test");
237        let hash_ref = BlockReference::at_hash(hash);
238        assert!(matches!(hash_ref, BlockReference::Hash(_)));
239    }
240
241    #[test]
242    fn test_block_reference_default() {
243        let default = BlockReference::default();
244        assert_eq!(default, BlockReference::Finality(Finality::Final));
245    }
246
247    #[test]
248    fn test_block_reference_from_finality() {
249        let block_ref: BlockReference = Finality::Optimistic.into();
250        assert_eq!(block_ref, BlockReference::Finality(Finality::Optimistic));
251    }
252
253    #[test]
254    fn test_block_reference_from_height() {
255        let block_ref: BlockReference = 99999u64.into();
256        assert_eq!(block_ref, BlockReference::Height(99999));
257    }
258
259    #[test]
260    fn test_block_reference_from_hash() {
261        let hash = CryptoHash::hash(b"block");
262        let block_ref: BlockReference = hash.into();
263        assert_eq!(block_ref, BlockReference::Hash(hash));
264    }
265
266    #[test]
267    fn test_finality_default() {
268        let default = Finality::default();
269        assert_eq!(default, Finality::Final);
270    }
271
272    #[test]
273    fn test_tx_execution_status_default() {
274        let default = TxExecutionStatus::default();
275        assert_eq!(default, TxExecutionStatus::ExecutedOptimistic);
276    }
277
278    #[test]
279    fn test_tx_execution_status_all_variants() {
280        assert_eq!(TxExecutionStatus::None.as_str(), "NONE");
281        assert_eq!(TxExecutionStatus::Included.as_str(), "INCLUDED");
282        assert_eq!(
283            TxExecutionStatus::ExecutedOptimistic.as_str(),
284            "EXECUTED_OPTIMISTIC"
285        );
286        assert_eq!(TxExecutionStatus::IncludedFinal.as_str(), "INCLUDED_FINAL");
287        assert_eq!(TxExecutionStatus::Executed.as_str(), "EXECUTED");
288        assert_eq!(TxExecutionStatus::Final.as_str(), "FINAL");
289    }
290
291    #[test]
292    fn test_finality_serde_roundtrip() {
293        // Test all variants serialize/deserialize correctly
294        for finality in [Finality::Optimistic, Finality::NearFinal, Finality::Final] {
295            let json = serde_json::to_string(&finality).unwrap();
296            let parsed: Finality = serde_json::from_str(&json).unwrap();
297            assert_eq!(finality, parsed);
298        }
299    }
300
301    #[test]
302    fn test_tx_execution_status_serde_roundtrip() {
303        for status in [
304            TxExecutionStatus::None,
305            TxExecutionStatus::Included,
306            TxExecutionStatus::ExecutedOptimistic,
307            TxExecutionStatus::IncludedFinal,
308            TxExecutionStatus::Executed,
309            TxExecutionStatus::Final,
310        ] {
311            let json = serde_json::to_string(&status).unwrap();
312            let parsed: TxExecutionStatus = serde_json::from_str(&json).unwrap();
313            assert_eq!(status, parsed);
314        }
315    }
316
317    #[test]
318    fn test_block_reference_clone_and_eq() {
319        let original = BlockReference::Height(12345);
320        let cloned = original.clone();
321        assert_eq!(original, cloned);
322    }
323
324    #[test]
325    fn test_finality_clone_and_copy() {
326        let f1 = Finality::Optimistic;
327        let f2 = f1; // Copy
328        #[allow(clippy::clone_on_copy)]
329        let f3 = f1.clone(); // Clone (intentionally testing Clone impl)
330        assert_eq!(f1, f2);
331        assert_eq!(f1, f3);
332    }
333
334    #[test]
335    fn test_sync_checkpoint_constructors() {
336        let genesis = BlockReference::genesis();
337        assert!(matches!(
338            genesis,
339            BlockReference::SyncCheckpoint(SyncCheckpoint::Genesis)
340        ));
341
342        let earliest = BlockReference::earliest_available();
343        assert!(matches!(
344            earliest,
345            BlockReference::SyncCheckpoint(SyncCheckpoint::EarliestAvailable)
346        ));
347    }
348
349    #[test]
350    fn test_sync_checkpoint_rpc_params() {
351        let genesis = BlockReference::genesis();
352        let params = genesis.to_rpc_params();
353        assert_eq!(params["sync_checkpoint"], "genesis");
354
355        let earliest = BlockReference::earliest_available();
356        let params = earliest.to_rpc_params();
357        assert_eq!(params["sync_checkpoint"], "earliest_available");
358    }
359
360    #[test]
361    fn test_sync_checkpoint_serde_roundtrip() {
362        for cp in [SyncCheckpoint::Genesis, SyncCheckpoint::EarliestAvailable] {
363            let json = serde_json::to_string(&cp).unwrap();
364            let parsed: SyncCheckpoint = serde_json::from_str(&json).unwrap();
365            assert_eq!(cp, parsed);
366        }
367    }
368}