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) => {
205 let txn = block_store.get_transaction(transaction_id)?;
206 txn.ok_or_else(|| anyhow!("Transaction {transaction_id} not in local storage"))
207 }
208 Self::REST(query) => query.get_transaction(transaction_id),
209 Self::STATIC(_query) => bail!("get_transaction is not supported by StaticQuery"),
210 }
211 }
212
213 #[cfg(feature = "async")]
215 pub async fn get_transaction_async(&self, transaction_id: &N::TransactionID) -> Result<Transaction<N>> {
216 match self {
217 Self::VM(block_store) => {
218 let txn = block_store.get_transaction(transaction_id)?;
219 txn.ok_or_else(|| anyhow!("Transaction {transaction_id} not in local storage"))
220 }
221 Self::REST(query) => query.get_transaction_async(transaction_id).await,
222 Self::STATIC(_query) => bail!("get_transaction is not supported by StaticQuery"),
223 }
224 }
225
226 pub fn get_program(&self, program_id: &ProgramID<N>) -> Result<Program<N>> {
228 match self {
229 Self::VM(block_store) => block_store
230 .get_latest_program(program_id)?
231 .ok_or_else(|| anyhow!("Program {program_id} not found in storage")),
232 Self::REST(query) => query.get_program(program_id),
233 Self::STATIC(_query) => bail!("get_program is not supported by StaticQuery"),
234 }
235 }
236
237 #[cfg(feature = "async")]
239 pub async fn get_program_async(&self, program_id: &ProgramID<N>) -> Result<Program<N>> {
240 match self {
241 Self::VM(block_store) => block_store
242 .get_latest_program(program_id)?
243 .with_context(|| format!("Program {program_id} not found in storage")),
244 Self::REST(query) => query.get_program_async(program_id).await,
245 Self::STATIC(_query) => bail!("get_program_async is not supported by StaticQuery"),
246 }
247 }
248}
249
250#[cfg(test)]
251mod tests {
252 use super::*;
253
254 use snarkvm_console::network::TestnetV0;
255 use snarkvm_ledger_store::helpers::memory::BlockMemory;
256
257 type CurrentNetwork = TestnetV0;
258 type CurrentQuery = Query<CurrentNetwork, BlockMemory<CurrentNetwork>>;
259
260 #[test]
261 fn test_static_query_parse() {
262 let json = r#"{"state_root": "sr1dz06ur5spdgzkguh4pr42mvft6u3nwsg5drh9rdja9v8jpcz3czsls9geg", "height": 14}"#
263 .to_string();
264 let query = CurrentQuery::try_from(json).unwrap();
265
266 assert!(matches!(query, Query::STATIC(_)));
267 }
268
269 #[test]
270 fn test_static_query_parse_invalid() {
271 let json = r#"{"invalid_key": "sr1dz06ur5spdgzkguh4pr42mvft6u3nwsg5drh9rdja9v8jpcz3czsls9geg", "height": 14}"#
272 .to_string();
273 let result = json.parse::<CurrentQuery>();
274
275 assert!(result.is_err());
276 }
277
278 #[test]
279 fn test_rest_url_parse_invalid_scheme() {
280 let str = "ftp://localhost:3030";
281 let result = CurrentQuery::try_from(str);
282
283 assert!(result.is_err());
284 }
285
286 #[test]
287 fn test_rest_url_parse_invalid_host() {
288 let str = "http://:3030";
289 let result = CurrentQuery::try_from(str);
290
291 assert!(result.is_err());
292 }
293}