snarkvm_ledger_query/query/
static_.rs

1// Copyright (c) 2019-2025 Provable Inc.
2// This file is part of the snarkVM 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
16use crate::QueryTrait;
17
18use snarkvm_console::{network::prelude::*, program::StatePath, types::Field};
19
20use anyhow::{Context, Result, ensure};
21use serde::Deserialize;
22use std::{collections::HashMap, str::FromStr};
23
24#[derive(Clone)]
25pub struct StaticQuery<N: Network> {
26    block_height: u32,
27    state_root: N::StateRoot,
28    state_paths: HashMap<Field<N>, StatePath<N>>,
29}
30
31impl<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)]
38struct StaticQueryInput {
39    state_root: String,
40    height: u32,
41}
42
43impl<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))]
57impl<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)]
114mod tests {
115    use super::*;
116    use snarkvm_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}