Skip to main content

hotshot_query_service/
merklized_state.rs

1// Copyright (c) 2022 Espresso Systems (espressosys.com)
2// This file is part of the HotShot Query Service library.
3//
4// This program is free software: you can redistribute it and/or modify it under the terms of the GNU
5// General Public License as published by the Free Software Foundation, either version 3 of the
6// License, or (at your option) any later version.
7// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
8// even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
9// General Public License for more details.
10// You should have received a copy of the GNU General Public License along with this program. If not,
11// see <https://www.gnu.org/licenses/>.
12
13//! Api for querying merklized state
14//!
15//! The state API provides an interface for serving queries against arbitrarily old snapshots of the state.
16//! This allows a full Merkle tree to be reconstructed from storage.
17//! If any parent state is missing then the partial snapshot can not be queried.
18use std::{fmt::Display, path::PathBuf};
19
20use futures::FutureExt;
21use hotshot_types::traits::node_implementation::NodeType;
22use snafu::ResultExt;
23use tagged_base64::TaggedBase64;
24use tide_disco::{Api, StatusCode, api::ApiError, method::ReadState};
25use vbs::version::StaticVersionType;
26
27use crate::api::load_api;
28
29pub(crate) mod data_source;
30pub use data_source::*;
31pub use hotshot_query_service_types::merklized_state::*;
32
33#[derive(Default)]
34pub struct Options {
35    pub api_path: Option<PathBuf>,
36
37    /// Additional API specification files to merge with `merklized-state-api-path`.
38    ///
39    /// These optional files may contain route definitions for application-specific routes that have
40    /// been added as extensions to the basic status API.
41    pub extensions: Vec<toml::Value>,
42}
43
44pub fn define_api<
45    State,
46    Types: NodeType,
47    M: MerklizedState<Types, ARITY>,
48    Ver: StaticVersionType + 'static,
49    const ARITY: usize,
50>(
51    options: &Options,
52    api_ver: semver::Version,
53) -> Result<Api<State, Error, Ver>, ApiError>
54where
55    State: 'static + Send + Sync + ReadState,
56    <State as ReadState>::State:
57        MerklizedStateDataSource<Types, M, ARITY> + MerklizedStateHeightPersistence + Send + Sync,
58    for<'a> <M::Commit as TryFrom<&'a TaggedBase64>>::Error: Display,
59{
60    let mut api = load_api::<State, Error, Ver>(
61        options.api_path.as_ref(),
62        include_str!("../api/state.toml"),
63        options.extensions.clone(),
64    )?;
65
66    api.with_version(api_ver)
67        .get("get_path", move |req, state| {
68            async move {
69                // Determine the snapshot type based on request parameters, either index or commit
70                let snapshot = if let Some(height) = req.opt_integer_param("height")? {
71                    Snapshot::Index(height)
72                } else {
73                    Snapshot::Commit(req.blob_param("commit")?)
74                };
75
76                let key = req.string_param("key")?;
77                let key = key.parse::<M::Key>().map_err(|_| Error::Custom {
78                    message: "failed to parse Key param".to_string(),
79                    status: StatusCode::INTERNAL_SERVER_ERROR,
80                })?;
81
82                state.get_path(snapshot, key).await.context(QuerySnafu)
83            }
84            .boxed()
85        })?
86        .get("get_height", move |_, state| {
87            async move { state.get_last_state_height().await.context(QuerySnafu) }.boxed()
88        })?;
89
90    Ok(api)
91}