/ ledger / query / src / query / static_.rs
static_.rs
  1  // Copyright (c) 2019-2025 Alpha-Delta Network Inc.
  2  // This file is part of the alphavm library.
  3  
  4  // Licensed under the Apache License, Version 2.0 (the "License");
  5  // you may not use this file except in compliance with the License.
  6  // You may obtain a copy of the License at:
  7  
  8  // http://www.apache.org/licenses/LICENSE-2.0
  9  
 10  // Unless required by applicable law or agreed to in writing, software
 11  // distributed under the License is distributed on an "AS IS" BASIS,
 12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  // See the License for the specific language governing permissions and
 14  // limitations under the License.
 15  
 16  use crate::QueryTrait;
 17  
 18  use alphavm_console::{network::prelude::*, program::StatePath, types::Field};
 19  
 20  use anyhow::{Context, Result, ensure};
 21  use serde::Deserialize;
 22  use std::{collections::HashMap, str::FromStr};
 23  
 24  #[derive(Clone)]
 25  pub struct StaticQuery<N: Network> {
 26      block_height: u32,
 27      state_root: N::StateRoot,
 28      state_paths: HashMap<Field<N>, StatePath<N>>,
 29  }
 30  
 31  impl<N: Network> StaticQuery<N> {
 32      pub fn new(block_height: u32, state_root: N::StateRoot, state_paths: HashMap<Field<N>, StatePath<N>>) -> Self {
 33          Self { block_height, state_root, state_paths }
 34      }
 35  }
 36  
 37  #[derive(Deserialize)]
 38  struct StaticQueryInput {
 39      state_root: String,
 40      height: u32,
 41  }
 42  
 43  impl<N: Network> FromStr for StaticQuery<N> {
 44      type Err = anyhow::Error;
 45  
 46      fn from_str(s: &str) -> Result<Self> {
 47          ensure!(s.trim().starts_with('{'), "Not a static query");
 48  
 49          let input: StaticQueryInput = serde_json::from_str(s).with_context(|| "Invalid JSON format in static query")?;
 50          let state_root = N::StateRoot::from_str(&input.state_root).map_err(|_| anyhow!("Invalid state root format"))?;
 51  
 52          Ok(Self { state_root, block_height: input.height, state_paths: HashMap::new() })
 53      }
 54  }
 55  
 56  #[cfg_attr(feature = "async", async_trait::async_trait(?Send))]
 57  impl<N: Network> QueryTrait<N> for StaticQuery<N> {
 58      /// Returns the current state root.
 59      fn current_state_root(&self) -> Result<N::StateRoot> {
 60          Ok(self.state_root)
 61      }
 62  
 63      /// Returns the current state root (async version).
 64      #[cfg(feature = "async")]
 65      async fn current_state_root_async(&self) -> Result<N::StateRoot> {
 66          // There is no I/O in StaticQuery, so the sync version is identical.
 67          self.current_state_root()
 68      }
 69  
 70      /// Returns a state path for the given `commitment`.
 71      fn get_state_path_for_commitment(&self, commitment: &Field<N>) -> Result<StatePath<N>> {
 72          match self.state_paths.get(commitment) {
 73              Some(state_path) => Ok(state_path.clone()),
 74              None => bail!("Could not find state path for commitment '{commitment}'"),
 75          }
 76      }
 77  
 78      /// Returns a state path for the given `commitment` (async version).
 79      #[cfg(feature = "async")]
 80      async fn get_state_path_for_commitment_async(&self, commitment: &Field<N>) -> Result<StatePath<N>> {
 81          // There is no I/O in StaticQuery, so the sync version is identical.
 82          self.get_state_path_for_commitment(commitment)
 83      }
 84  
 85      /// Returns a list of state paths for the given list of `commitment`s.
 86      fn get_state_paths_for_commitments(&self, commitments: &[Field<N>]) -> Result<Vec<StatePath<N>>> {
 87          commitments
 88              .iter()
 89              .map(|commitment| self.get_state_path_for_commitment(commitment))
 90              .collect::<Result<Vec<StatePath<N>>>>()
 91      }
 92  
 93      /// Returns a list of state paths for the given list of `commitment`s (async version).
 94      #[cfg(feature = "async")]
 95      async fn get_state_paths_for_commitments_async(&self, commitments: &[Field<N>]) -> Result<Vec<StatePath<N>>> {
 96          // There is no I/O in StaticQuery, so the sync version is identical.
 97          self.get_state_paths_for_commitments(commitments)
 98      }
 99  
100      /// Returns the current block height.
101      fn current_block_height(&self) -> Result<u32> {
102          Ok(self.block_height)
103      }
104  
105      /// Returns the current block height (async version).
106      #[cfg(feature = "async")]
107      async fn current_block_height_async(&self) -> Result<u32> {
108          // There is no I/O in StaticQuery, so the sync version is identical.
109          self.current_block_height()
110      }
111  }
112  
113  #[cfg(test)]
114  mod tests {
115      use super::*;
116      use alphavm_console::network::TestnetV0;
117  
118      #[test]
119      fn test_static_query_parse() {
120          let json = r#"{"state_root": "sr1dz06ur5spdgzkguh4pr42mvft6u3nwsg5drh9rdja9v8jpcz3czsls9geg", "height": 14}"#
121              .to_string();
122          let query: Result<StaticQuery<TestnetV0>> = json.parse();
123          assert!(query.is_ok());
124      }
125  
126      #[test]
127      fn test_static_query_parse_invalid() {
128          let json = r#"{"invalid_key": "sr1dz06ur5spdgzkguh4pr42mvft6u3nwsg5drh9rdja9v8jpcz3czsls9geg", "height": 14}"#
129              .to_string();
130          let query: Result<StaticQuery<TestnetV0>> = json.parse();
131          assert!(query.is_err());
132      }
133  }