query.rs
1 // Copyright (c) 2025-2026 ACDC Network 2 // This file is part of the alphavm library. 3 // 4 // Alpha Chain | Delta Chain Protocol 5 // International Monetary Graphite. 6 // 7 // Derived from Aleo (https://aleo.org) and ProvableHQ (https://provable.com). 8 // They built world-class ZK infrastructure. We installed the EASY button. 9 // Their cryptography: elegant. Our modifications: bureaucracy-compatible. 10 // Original brilliance: theirs. Robert's Rules: ours. Bugs: definitely ours. 11 // 12 // Original Aleo/ProvableHQ code subject to Apache 2.0 https://www.apache.org/licenses/LICENSE-2.0 13 // All modifications and new work: CC0 1.0 Universal Public Domain Dedication. 14 // No rights reserved. No permission required. No warranty. No refunds. 15 // 16 // https://creativecommons.org/publicdomain/zero/1.0/ 17 // SPDX-License-Identifier: CC0-1.0 18 19 use crate::QueryTrait; 20 21 use alphavm_console::{ 22 network::prelude::*, 23 program::{ProgramID, StatePath}, 24 types::Field, 25 }; 26 use alphavm_ledger_block::Transaction; 27 use alphavm_ledger_store::{BlockStorage, BlockStore}; 28 use alphavm_synthesizer_program::Program; 29 30 use anyhow::{Context, Result}; 31 // ureq re-exports the `http` crate. 32 use ureq::http; 33 34 mod static_; 35 pub use static_::StaticQuery; 36 37 mod rest; 38 pub use rest::RestQuery; 39 40 /// Make the REST error type available public as it can be used for any API endpoint. 41 pub use rest::RestError; 42 43 /// Allows inspecting the state of the blockstain using either local state or a remote endpoint. 44 #[derive(Clone)] 45 pub enum Query<N: Network, B: BlockStorage<N>> { 46 /// Query state in a local block store. 47 VM(BlockStore<N, B>), 48 /// Query state using a node's REST API. 49 REST(RestQuery<N>), 50 // Return static state for testing and performance. 51 STATIC(StaticQuery<N>), 52 } 53 54 /// Initialize the `Query` object from a local `BlockStore`. 55 impl<N: Network, B: BlockStorage<N>> From<BlockStore<N, B>> for Query<N, B> { 56 fn from(block_store: BlockStore<N, B>) -> Self { 57 Self::VM(block_store) 58 } 59 } 60 61 /// Initialize the `Query` object from a local `BlockStore`. 62 impl<N: Network, B: BlockStorage<N>> From<&BlockStore<N, B>> for Query<N, B> { 63 fn from(block_store: &BlockStore<N, B>) -> Self { 64 Self::VM(block_store.clone()) 65 } 66 } 67 68 /// Initialize the `Query` object from an endpoint URL. The URI should point to a snarkOS node's REST API. 69 impl<N: Network, B: BlockStorage<N>> From<http::Uri> for Query<N, B> { 70 fn from(uri: http::Uri) -> Self { 71 Self::REST(RestQuery::from(uri)) 72 } 73 } 74 75 /// Initialize the `Query` object from an endpoint URL (passed as a string). The URI should point to a snarkOS node's REST API. 76 impl<N: Network, B: BlockStorage<N>> TryFrom<String> for Query<N, B> { 77 type Error = anyhow::Error; 78 79 fn try_from(string_representation: String) -> Result<Self> { 80 Self::try_from(string_representation.as_str()) 81 } 82 } 83 84 /// Initialize the `Query` object from an endpoint URL (passed as a string). The URI should point to a snarkOS node's REST API. 85 impl<N: Network, B: BlockStorage<N>> TryFrom<&String> for Query<N, B> { 86 type Error = anyhow::Error; 87 88 fn try_from(string_representation: &String) -> Result<Self> { 89 Self::try_from(string_representation.as_str()) 90 } 91 } 92 93 /// Initialize the `Query` object from an endpoint URL (passed as a string). The URI should point to a snarkOS node's REST API. 94 impl<N: Network, B: BlockStorage<N>> TryFrom<&str> for Query<N, B> { 95 type Error = anyhow::Error; 96 97 fn try_from(str_representation: &str) -> Result<Self> { 98 str_representation.parse::<Self>() 99 } 100 } 101 102 /// Initialize the `Query` object from an endpoint URL (passed as a string). The URI should point to a snarkOS node's REST API. 103 impl<N: Network, B: BlockStorage<N>> FromStr for Query<N, B> { 104 type Err = anyhow::Error; 105 106 fn from_str(str_representation: &str) -> Result<Self> { 107 // A static query is represented as JSON and a valid URI does not start with `}`. 108 if str_representation.trim().starts_with('{') { 109 let static_query = 110 str_representation.parse::<StaticQuery<N>>().with_context(|| "Failed to parse static query")?; 111 Ok(Self::STATIC(static_query)) 112 } else { 113 let rest_query = RestQuery::from_str(str_representation).with_context(|| "Failed to parse query")?; 114 Ok(Self::REST(rest_query)) 115 } 116 } 117 } 118 119 #[cfg_attr(feature = "async", async_trait::async_trait(?Send))] 120 impl<N: Network, B: BlockStorage<N>> QueryTrait<N> for Query<N, B> { 121 /// Returns the current state root. 122 fn current_state_root(&self) -> Result<N::StateRoot> { 123 match self { 124 Self::VM(block_store) => Ok(block_store.current_state_root()), 125 Self::REST(query) => query.current_state_root(), 126 Self::STATIC(query) => query.current_state_root(), 127 } 128 } 129 130 /// Returns the current state root. 131 #[cfg(feature = "async")] 132 async fn current_state_root_async(&self) -> Result<N::StateRoot> { 133 match self { 134 Self::VM(block_store) => Ok(block_store.current_state_root()), 135 Self::REST(query) => query.current_state_root_async().await, 136 Self::STATIC(query) => query.current_state_root_async().await, 137 } 138 } 139 140 /// Returns a state path for the given `commitment`. 141 fn get_state_path_for_commitment(&self, commitment: &Field<N>) -> Result<StatePath<N>> { 142 match self { 143 Self::VM(block_store) => block_store.get_state_path_for_commitment(commitment), 144 Self::REST(query) => query.get_state_path_for_commitment(commitment), 145 Self::STATIC(query) => query.get_state_path_for_commitment(commitment), 146 } 147 } 148 149 /// Returns a state path for the given `commitment`. 150 #[cfg(feature = "async")] 151 async fn get_state_path_for_commitment_async(&self, commitment: &Field<N>) -> Result<StatePath<N>> { 152 match self { 153 Self::VM(block_store) => block_store.get_state_path_for_commitment(commitment), 154 Self::REST(query) => query.get_state_path_for_commitment_async(commitment).await, 155 Self::STATIC(query) => query.get_state_path_for_commitment_async(commitment).await, 156 } 157 } 158 159 /// Returns a list of state paths for the given list of `commitment`s. 160 fn get_state_paths_for_commitments(&self, commitments: &[Field<N>]) -> Result<Vec<StatePath<N>>> { 161 // Return an empty vector if there are no commitments. 162 if commitments.is_empty() { 163 return Ok(vec![]); 164 } 165 166 match self { 167 Self::VM(block_store) => block_store.get_state_paths_for_commitments(commitments), 168 Self::REST(query) => query.get_state_paths_for_commitments(commitments), 169 Self::STATIC(query) => query.get_state_paths_for_commitments(commitments), 170 } 171 } 172 173 /// Returns a list of state paths for the given list of `commitment`s. 174 #[cfg(feature = "async")] 175 async fn get_state_paths_for_commitments_async(&self, commitments: &[Field<N>]) -> Result<Vec<StatePath<N>>> { 176 match self { 177 Self::VM(block_store) => block_store.get_state_paths_for_commitments(commitments), 178 Self::REST(query) => query.get_state_paths_for_commitments_async(commitments).await, 179 Self::STATIC(query) => query.get_state_paths_for_commitments(commitments), 180 } 181 } 182 183 /// Returns a state path for the given `commitment`. 184 fn current_block_height(&self) -> Result<u32> { 185 match self { 186 Self::VM(block_store) => Ok(block_store.max_height().unwrap_or_default()), 187 Self::REST(query) => query.current_block_height(), 188 Self::STATIC(query) => query.current_block_height(), 189 } 190 } 191 192 /// Returns a state path for the given `commitment`. 193 #[cfg(feature = "async")] 194 async fn current_block_height_async(&self) -> Result<u32> { 195 match self { 196 Self::VM(block_store) => Ok(block_store.max_height().unwrap_or_default()), 197 Self::REST(query) => query.current_block_height_async().await, 198 Self::STATIC(query) => query.current_block_height_async().await, 199 } 200 } 201 } 202 203 impl<N: Network, B: BlockStorage<N>> Query<N, B> { 204 /// Returns the transaction for the given transaction ID. 205 pub fn get_transaction(&self, transaction_id: &N::TransactionID) -> Result<Transaction<N>> { 206 match self { 207 Self::VM(block_store) => { 208 let txn = block_store.get_transaction(transaction_id)?; 209 txn.ok_or_else(|| anyhow!("Transaction {transaction_id} not in local storage")) 210 } 211 Self::REST(query) => query.get_transaction(transaction_id), 212 Self::STATIC(_query) => bail!("get_transaction is not supported by StaticQuery"), 213 } 214 } 215 216 /// Returns the transaction for the given transaction ID. 217 #[cfg(feature = "async")] 218 pub async fn get_transaction_async(&self, transaction_id: &N::TransactionID) -> Result<Transaction<N>> { 219 match self { 220 Self::VM(block_store) => { 221 let txn = block_store.get_transaction(transaction_id)?; 222 txn.ok_or_else(|| anyhow!("Transaction {transaction_id} not in local storage")) 223 } 224 Self::REST(query) => query.get_transaction_async(transaction_id).await, 225 Self::STATIC(_query) => bail!("get_transaction is not supported by StaticQuery"), 226 } 227 } 228 229 /// Returns the program for the given program ID. 230 pub fn get_program(&self, program_id: &ProgramID<N>) -> Result<Program<N>> { 231 match self { 232 Self::VM(block_store) => block_store 233 .get_latest_program(program_id)? 234 .ok_or_else(|| anyhow!("Program {program_id} not found in storage")), 235 Self::REST(query) => query.get_program(program_id), 236 Self::STATIC(_query) => bail!("get_program is not supported by StaticQuery"), 237 } 238 } 239 240 /// Returns the program for the given program ID. 241 #[cfg(feature = "async")] 242 pub async fn get_program_async(&self, program_id: &ProgramID<N>) -> Result<Program<N>> { 243 match self { 244 Self::VM(block_store) => block_store 245 .get_latest_program(program_id)? 246 .with_context(|| format!("Program {program_id} not found in storage")), 247 Self::REST(query) => query.get_program_async(program_id).await, 248 Self::STATIC(_query) => bail!("get_program_async is not supported by StaticQuery"), 249 } 250 } 251 } 252 253 #[cfg(test)] 254 mod tests { 255 use super::*; 256 257 use alphavm_console::network::TestnetV0; 258 use alphavm_ledger_store::helpers::memory::BlockMemory; 259 260 type CurrentNetwork = TestnetV0; 261 type CurrentQuery = Query<CurrentNetwork, BlockMemory<CurrentNetwork>>; 262 263 #[test] 264 fn test_static_query_parse() { 265 let json = r#"{"state_root": "sr1dz06ur5spdgzkguh4pr42mvft6u3nwsg5drh9rdja9v8jpcz3czsls9geg", "height": 14}"# 266 .to_string(); 267 let query = CurrentQuery::try_from(json).unwrap(); 268 269 assert!(matches!(query, Query::STATIC(_))); 270 } 271 272 #[test] 273 fn test_static_query_parse_invalid() { 274 let json = r#"{"invalid_key": "sr1dz06ur5spdgzkguh4pr42mvft6u3nwsg5drh9rdja9v8jpcz3czsls9geg", "height": 14}"# 275 .to_string(); 276 let result = json.parse::<CurrentQuery>(); 277 278 assert!(result.is_err()); 279 } 280 281 #[test] 282 fn test_rest_url_parse_invalid_scheme() { 283 let str = "ftp://localhost:3030"; 284 let result = CurrentQuery::try_from(str); 285 286 assert!(result.is_err()); 287 } 288 289 #[test] 290 fn test_rest_url_parse_invalid_host() { 291 let str = "http://:3030"; 292 let result = CurrentQuery::try_from(str); 293 294 assert!(result.is_err()); 295 } 296 }