snarkvm_ledger_query/
query.rs1use crate::QueryTrait;
17
18use snarkvm_console::{
19 network::prelude::*,
20 program::{ProgramID, StatePath},
21 types::Field,
22};
23use snarkvm_ledger_block::Transaction;
24use snarkvm_ledger_store::{BlockStorage, BlockStore};
25use snarkvm_synthesizer_program::Program;
26
27use anyhow::{Context, Result};
28use ureq::http;
30
31mod static_;
32pub use static_::StaticQuery;
33
34mod rest;
35pub use rest::RestQuery;
36
37pub use rest::RestError;
39
40#[derive(Clone)]
42pub enum Query<N: Network, B: BlockStorage<N>> {
43 VM(BlockStore<N, B>),
45 REST(RestQuery<N>),
47 STATIC(StaticQuery<N>),
49}
50
51impl<N: Network, B: BlockStorage<N>> From<BlockStore<N, B>> for Query<N, B> {
53 fn from(block_store: BlockStore<N, B>) -> Self {
54 Self::VM(block_store)
55 }
56}
57
58impl<N: Network, B: BlockStorage<N>> From<&BlockStore<N, B>> for Query<N, B> {
60 fn from(block_store: &BlockStore<N, B>) -> Self {
61 Self::VM(block_store.clone())
62 }
63}
64
65impl<N: Network, B: BlockStorage<N>> From<http::Uri> for Query<N, B> {
67 fn from(uri: http::Uri) -> Self {
68 Self::REST(RestQuery::from(uri))
69 }
70}
71
72impl<N: Network, B: BlockStorage<N>> TryFrom<String> for Query<N, B> {
74 type Error = anyhow::Error;
75
76 fn try_from(string_representation: String) -> Result<Self> {
77 Self::try_from(string_representation.as_str())
78 }
79}
80
81impl<N: Network, B: BlockStorage<N>> TryFrom<&String> for Query<N, B> {
83 type Error = anyhow::Error;
84
85 fn try_from(string_representation: &String) -> Result<Self> {
86 Self::try_from(string_representation.as_str())
87 }
88}
89
90impl<N: Network, B: BlockStorage<N>> TryFrom<&str> for Query<N, B> {
92 type Error = anyhow::Error;
93
94 fn try_from(str_representation: &str) -> Result<Self> {
95 str_representation.parse::<Self>()
96 }
97}
98
99impl<N: Network, B: BlockStorage<N>> FromStr for Query<N, B> {
101 type Err = anyhow::Error;
102
103 fn from_str(str_representation: &str) -> Result<Self> {
104 if str_representation.trim().starts_with('{') {
106 let static_query =
107 str_representation.parse::<StaticQuery<N>>().with_context(|| "Failed to parse static query")?;
108 Ok(Self::STATIC(static_query))
109 } else {
110 let rest_query = RestQuery::from_str(str_representation).with_context(|| "Failed to parse query")?;
111 Ok(Self::REST(rest_query))
112 }
113 }
114}
115
116#[cfg_attr(feature = "async", async_trait::async_trait(?Send))]
117impl<N: Network, B: BlockStorage<N>> QueryTrait<N> for Query<N, B> {
118 fn current_state_root(&self) -> Result<N::StateRoot> {
120 match self {
121 Self::VM(block_store) => Ok(block_store.current_state_root()),
122 Self::REST(query) => query.current_state_root(),
123 Self::STATIC(query) => query.current_state_root(),
124 }
125 }
126
127 #[cfg(feature = "async")]
129 async fn current_state_root_async(&self) -> Result<N::StateRoot> {
130 match self {
131 Self::VM(block_store) => Ok(block_store.current_state_root()),
132 Self::REST(query) => query.current_state_root_async().await,
133 Self::STATIC(query) => query.current_state_root_async().await,
134 }
135 }
136
137 fn get_state_path_for_commitment(&self, commitment: &Field<N>) -> Result<StatePath<N>> {
139 match self {
140 Self::VM(block_store) => block_store.get_state_path_for_commitment(commitment),
141 Self::REST(query) => query.get_state_path_for_commitment(commitment),
142 Self::STATIC(query) => query.get_state_path_for_commitment(commitment),
143 }
144 }
145
146 #[cfg(feature = "async")]
148 async fn get_state_path_for_commitment_async(&self, commitment: &Field<N>) -> Result<StatePath<N>> {
149 match self {
150 Self::VM(block_store) => block_store.get_state_path_for_commitment(commitment),
151 Self::REST(query) => query.get_state_path_for_commitment_async(commitment).await,
152 Self::STATIC(query) => query.get_state_path_for_commitment_async(commitment).await,
153 }
154 }
155
156 fn get_state_paths_for_commitments(&self, commitments: &[Field<N>]) -> Result<Vec<StatePath<N>>> {
158 if commitments.is_empty() {
160 return Ok(vec![]);
161 }
162
163 match self {
164 Self::VM(block_store) => block_store.get_state_paths_for_commitments(commitments),
165 Self::REST(query) => query.get_state_paths_for_commitments(commitments),
166 Self::STATIC(query) => query.get_state_paths_for_commitments(commitments),
167 }
168 }
169
170 #[cfg(feature = "async")]
172 async fn get_state_paths_for_commitments_async(&self, commitments: &[Field<N>]) -> Result<Vec<StatePath<N>>> {
173 match self {
174 Self::VM(block_store) => block_store.get_state_paths_for_commitments(commitments),
175 Self::REST(query) => query.get_state_paths_for_commitments_async(commitments).await,
176 Self::STATIC(query) => query.get_state_paths_for_commitments(commitments),
177 }
178 }
179
180 fn current_block_height(&self) -> Result<u32> {
182 match self {
183 Self::VM(block_store) => Ok(block_store.max_height().unwrap_or_default()),
184 Self::REST(query) => query.current_block_height(),
185 Self::STATIC(query) => query.current_block_height(),
186 }
187 }
188
189 #[cfg(feature = "async")]
191 async fn current_block_height_async(&self) -> Result<u32> {
192 match self {
193 Self::VM(block_store) => Ok(block_store.max_height().unwrap_or_default()),
194 Self::REST(query) => query.current_block_height_async().await,
195 Self::STATIC(query) => query.current_block_height_async().await,
196 }
197 }
198}
199
200impl<N: Network, B: BlockStorage<N>> Query<N, B> {
201 pub fn get_transaction(&self, transaction_id: &N::TransactionID) -> Result<Transaction<N>> {
203 match self {
204 Self::VM(block_store) => block_store
205 .get_transaction(transaction_id)?
206 .ok_or_else(|| anyhow!("Missing transaction '{transaction_id}' in block storage")),
207 Self::REST(query) => query.get_transaction(transaction_id),
208 Self::STATIC(_query) => bail!("get_transaction is not supported by StaticQuery"),
209 }
210 }
211
212 #[cfg(feature = "async")]
214 pub async fn get_transaction_async(&self, transaction_id: &N::TransactionID) -> Result<Transaction<N>> {
215 match self {
216 Self::VM(block_store) => block_store
217 .get_transaction(transaction_id)?
218 .ok_or_else(|| anyhow!("Missing transaction '{transaction_id}' in block storage")),
219 Self::REST(query) => query.get_transaction_async(transaction_id).await,
220 Self::STATIC(_query) => bail!("get_transaction is not supported by StaticQuery"),
221 }
222 }
223
224 pub fn get_program(&self, program_id: &ProgramID<N>) -> Result<Program<N>> {
226 match self {
227 Self::VM(block_store) => block_store
228 .get_latest_program(program_id)?
229 .ok_or_else(|| anyhow!("Program {program_id} not found in storage")),
230 Self::REST(query) => query.get_program(program_id),
231 Self::STATIC(_query) => bail!("get_program is not supported by StaticQuery"),
232 }
233 }
234
235 #[cfg(feature = "async")]
237 pub async fn get_program_async(&self, program_id: &ProgramID<N>) -> Result<Program<N>> {
238 match self {
239 Self::VM(block_store) => block_store
240 .get_latest_program(program_id)?
241 .with_context(|| format!("Program {program_id} not found in storage")),
242 Self::REST(query) => query.get_program_async(program_id).await,
243 Self::STATIC(_query) => bail!("get_program_async is not supported by StaticQuery"),
244 }
245 }
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251
252 use snarkvm_console::network::TestnetV0;
253 use snarkvm_ledger_store::helpers::memory::BlockMemory;
254
255 type CurrentNetwork = TestnetV0;
256 type CurrentQuery = Query<CurrentNetwork, BlockMemory<CurrentNetwork>>;
257
258 #[test]
259 fn test_static_query_parse() {
260 let json = r#"{"state_root": "sr1dz06ur5spdgzkguh4pr42mvft6u3nwsg5drh9rdja9v8jpcz3czsls9geg", "height": 14}"#
261 .to_string();
262 let query = CurrentQuery::try_from(json).unwrap();
263
264 assert!(matches!(query, Query::STATIC(_)));
265 }
266
267 #[test]
268 fn test_static_query_parse_invalid() {
269 let json = r#"{"invalid_key": "sr1dz06ur5spdgzkguh4pr42mvft6u3nwsg5drh9rdja9v8jpcz3czsls9geg", "height": 14}"#
270 .to_string();
271 let result = json.parse::<CurrentQuery>();
272
273 assert!(result.is_err());
274 }
275
276 #[test]
277 fn test_rest_url_parse_invalid_scheme() {
278 let str = "ftp://localhost:3030";
279 let result = CurrentQuery::try_from(str);
280
281 assert!(result.is_err());
282 }
283
284 #[test]
285 fn test_rest_url_parse_invalid_host() {
286 let str = "http://:3030";
287 let result = CurrentQuery::try_from(str);
288
289 assert!(result.is_err());
290 }
291}