diff --git a/grovedb/src/debugger.rs b/grovedb/src/debugger.rs index de76c6df..e3556acd 100644 --- a/grovedb/src/debugger.rs +++ b/grovedb/src/debugger.rs @@ -1,19 +1,31 @@ //! GroveDB debugging support module. -use std::{fs, net::Ipv4Addr, sync::Weak}; +use std::{collections::BTreeMap, fs, sync::Weak}; use axum::{extract::State, http::StatusCode, response::IntoResponse, routing::post, Json, Router}; -use grovedb_merk::debugger::NodeDbg; +use grovedb_merk::{ + debugger::NodeDbg, + proofs::{Decoder, Node, Op}, + TreeFeatureType, +}; use grovedb_path::SubtreePath; use grovedb_version::version::GroveVersion; -use grovedbg_types::{NodeFetchRequest, NodeUpdate, Path}; +use grovedbg_types::{ + MerkProofNode, MerkProofOp, NodeFetchRequest, NodeUpdate, Path, PathQuery, Query, QueryItem, + SizedQuery, SubqueryBranch, +}; +use indexmap::IndexMap; use tokio::{ net::ToSocketAddrs, sync::mpsc::{self, Sender}, }; use tower_http::services::ServeDir; -use crate::{reference_path::ReferencePathType, GroveDb}; +use crate::{ + operations::proof::{GroveDBProof, LayerProof, ProveOptions}, + reference_path::ReferencePathType, + GroveDb, +}; const GROVEDBG_ZIP: [u8; include_bytes!(concat!(env!("OUT_DIR"), "/grovedbg.zip")).len()] = *include_bytes!(concat!(env!("OUT_DIR"), "/grovedbg.zip")); @@ -36,6 +48,7 @@ where let app = Router::new() .route("/fetch_node", post(fetch_node)) .route("/fetch_root_node", post(fetch_root_node)) + .route("/execute_path_query", post(execute_path_query)) .fallback_service(ServeDir::new(grovedbg_www)) .with_state((shutdown_send, grovedb)); @@ -123,62 +136,290 @@ async fn fetch_root_node( } } -fn node_to_update( - path: Path, - NodeDbg { - key, - value, - left_child, - right_child, - }: NodeDbg, -) -> Result { - // todo: GroveVersion::latest() to actual version - let grovedb_element = crate::Element::deserialize(&value, GroveVersion::latest())?; +async fn execute_path_query( + State((shutdown, grovedb)): State<(Sender<()>, Weak)>, + Json(json_path_query): Json, +) -> Result, AppError> { + let Some(db) = grovedb.upgrade() else { + shutdown.send(()).await.ok(); + return Err(AppError::Closed); + }; + + let path_query = path_query_to_grovedb(json_path_query); + + let grovedb_proof = db + .prove_internal(&path_query, None, GroveVersion::latest()) + .unwrap()?; + Ok(Json(proof_to_grovedbg(grovedb_proof)?)) +} + +fn proof_to_grovedbg(proof: GroveDBProof) -> Result { + match proof { + GroveDBProof::V0(p) => Ok(grovedbg_types::Proof { + root_layer: proof_layer_to_grovedbg(p.root_layer)?, + prove_options: prove_options_to_grovedbg(p.prove_options), + }), + } +} + +fn proof_layer_to_grovedbg( + proof_layer: LayerProof, +) -> Result { + Ok(grovedbg_types::ProofLayer { + merk_proof: merk_proof_to_grovedbg(&proof_layer.merk_proof)?, + lower_layers: proof_layer + .lower_layers + .into_iter() + .map(|(k, v)| proof_layer_to_grovedbg(v).map(|layer| (k, layer))) + .collect::, grovedbg_types::ProofLayer>, crate::Error>>()?, + }) +} + +fn merk_proof_to_grovedbg(merk_proof: &[u8]) -> Result, crate::Error> { + let decoder = Decoder::new(merk_proof); + decoder + .map(|op_result| { + op_result + .map_err(crate::Error::MerkError) + .and_then(merk_proof_op_to_grovedbg) + }) + .collect::, _>>() +} +fn merk_proof_op_to_grovedbg(op: Op) -> Result { + Ok(match op { + Op::Push(node) => MerkProofOp::Push(merk_proof_node_to_grovedbg(node)?), + Op::PushInverted(node) => MerkProofOp::PushInverted(merk_proof_node_to_grovedbg(node)?), + Op::Parent => MerkProofOp::Parent, + Op::Child => MerkProofOp::Child, + Op::ParentInverted => MerkProofOp::ParentInverted, + Op::ChildInverted => MerkProofOp::ChildInverted, + }) +} + +fn merk_proof_node_to_grovedbg(node: Node) -> Result { + Ok(match node { + Node::Hash(hash) => MerkProofNode::Hash(hash), + Node::KVHash(hash) => MerkProofNode::KVHash(hash), + Node::KVDigest(key, hash) => MerkProofNode::KVDigest(key, hash), + Node::KV(key, value) => { + let element = crate::Element::deserialize(&value, GroveVersion::latest())?; + MerkProofNode::KV(key, element_to_grovedbg(element)) + } + Node::KVValueHash(key, value, hash) => { + let element = crate::Element::deserialize(&value, GroveVersion::latest())?; + MerkProofNode::KVValueHash(key, element_to_grovedbg(element), hash) + } + Node::KVValueHashFeatureType(key, value, hash, TreeFeatureType::BasicMerkNode) => { + let element = crate::Element::deserialize(&value, GroveVersion::latest())?; + MerkProofNode::KVValueHashFeatureType( + key, + element_to_grovedbg(element), + hash, + grovedbg_types::TreeFeatureType::BasicMerkNode, + ) + } + Node::KVValueHashFeatureType(key, value, hash, TreeFeatureType::SummedMerkNode(sum)) => { + let element = crate::Element::deserialize(&value, GroveVersion::latest())?; + MerkProofNode::KVValueHashFeatureType( + key, + element_to_grovedbg(element), + hash, + grovedbg_types::TreeFeatureType::SummedMerkNode(sum), + ) + } + Node::KVRefValueHash(key, value, hash) => { + let element = crate::Element::deserialize(&value, GroveVersion::latest())?; + MerkProofNode::KVRefValueHash(key, element_to_grovedbg(element), hash) + } + }) +} + +fn prove_options_to_grovedbg(options: ProveOptions) -> grovedbg_types::ProveOptions { + grovedbg_types::ProveOptions { + decrease_limit_on_empty_sub_query_result: options.decrease_limit_on_empty_sub_query_result, + } +} + +fn path_query_to_grovedb(query: PathQuery) -> crate::PathQuery { + let PathQuery { + path, + query: + SizedQuery { + limit, + offset, + query: inner_query, + }, + } = query; + + crate::PathQuery { + path, + query: crate::SizedQuery { + query: query_to_grovedb(inner_query), + limit, + offset, + }, + } +} - let element = match grovedb_element { - crate::Element::Item(value, ..) => grovedbg_types::Element::Item { value }, - crate::Element::Tree(root_key, ..) => grovedbg_types::Element::Subtree { root_key }, - crate::Element::Reference(ReferencePathType::AbsolutePathReference(path), ..) => { - grovedbg_types::Element::AbsolutePathReference { path } +fn query_to_grovedb(query: Query) -> crate::Query { + crate::Query { + items: query.items.into_iter().map(query_item_to_grovedb).collect(), + default_subquery_branch: subquery_branch_to_grovedb(query.default_subquery_branch), + conditional_subquery_branches: conditional_subquery_branches_to_grovedb( + query.conditional_subquery_branches, + ), + left_to_right: query.left_to_right, + } +} + +fn conditional_subquery_branches_to_grovedb( + conditional_subquery_branches: Vec<(QueryItem, SubqueryBranch)>, +) -> Option> { + if conditional_subquery_branches.is_empty() { + None + } else { + Some( + conditional_subquery_branches + .into_iter() + .map(|(item, branch)| { + ( + query_item_to_grovedb(item), + subquery_branch_to_grovedb(branch), + ) + }) + .collect(), + ) + } +} + +fn subquery_branch_to_grovedb( + subquery_branch: SubqueryBranch, +) -> grovedb_merk::proofs::query::SubqueryBranch { + grovedb_merk::proofs::query::SubqueryBranch { + subquery_path: subquery_branch.subquery_path, + subquery: subquery_branch + .subquery + .map(|q| Box::new(query_to_grovedb(*q))), + } +} + +fn query_item_to_grovedb(item: QueryItem) -> crate::QueryItem { + match item { + QueryItem::Key(x) => crate::QueryItem::Key(x), + QueryItem::Range { start, end } => crate::QueryItem::Range(start..end), + QueryItem::RangeInclusive { start, end } => crate::QueryItem::RangeInclusive(start..=end), + QueryItem::RangeFull => crate::QueryItem::RangeFull(..), + QueryItem::RangeFrom(x) => crate::QueryItem::RangeFrom(x..), + QueryItem::RangeTo(x) => crate::QueryItem::RangeTo(..x), + QueryItem::RangeToInclusive(x) => crate::QueryItem::RangeToInclusive(..=x), + QueryItem::RangeAfter(x) => crate::QueryItem::RangeAfter(x..), + QueryItem::RangeAfterTo { after, to } => crate::QueryItem::RangeAfterTo(after..to), + QueryItem::RangeAfterToInclusive { after, to } => { + crate::QueryItem::RangeAfterToInclusive(after..=to) } + } +} + +fn element_to_grovedbg(element: crate::Element) -> grovedbg_types::Element { + match element { + crate::Element::Item(value, element_flags) => grovedbg_types::Element::Item { + value, + element_flags, + }, + crate::Element::Tree(root_key, element_flags) => grovedbg_types::Element::Subtree { + root_key, + element_flags, + }, + crate::Element::Reference( + ReferencePathType::AbsolutePathReference(path), + _, + element_flags, + ) => grovedbg_types::Element::AbsolutePathReference { + path, + element_flags, + }, crate::Element::Reference( ReferencePathType::UpstreamRootHeightReference(n_keep, path_append), - .., + _, + element_flags, ) => grovedbg_types::Element::UpstreamRootHeightReference { n_keep: n_keep.into(), path_append, + element_flags, }, crate::Element::Reference( ReferencePathType::UpstreamRootHeightWithParentPathAdditionReference( n_keep, path_append, ), - .., + _, + element_flags, ) => grovedbg_types::Element::UpstreamRootHeightWithParentPathAdditionReference { n_keep: n_keep.into(), path_append, + element_flags, }, crate::Element::Reference( ReferencePathType::UpstreamFromElementHeightReference(n_remove, path_append), - .., + _, + element_flags, ) => grovedbg_types::Element::UpstreamFromElementHeightReference { n_remove: n_remove.into(), path_append, + element_flags, }, - crate::Element::Reference(ReferencePathType::CousinReference(swap_parent), ..) => { - grovedbg_types::Element::CousinReference { swap_parent } - } - crate::Element::Reference(ReferencePathType::RemovedCousinReference(swap_parent), ..) => { - grovedbg_types::Element::RemovedCousinReference { swap_parent } - } - crate::Element::Reference(ReferencePathType::SiblingReference(sibling_key), ..) => { - grovedbg_types::Element::SiblingReference { sibling_key } - } - crate::Element::SumItem(value, _) => grovedbg_types::Element::SumItem { value }, - crate::Element::SumTree(root_key, sum, _) => { - grovedbg_types::Element::Sumtree { root_key, sum } - } - }; + crate::Element::Reference( + ReferencePathType::CousinReference(swap_parent), + _, + element_flags, + ) => grovedbg_types::Element::CousinReference { + swap_parent, + element_flags, + }, + crate::Element::Reference( + ReferencePathType::RemovedCousinReference(swap_parent), + _, + element_flags, + ) => grovedbg_types::Element::RemovedCousinReference { + swap_parent, + element_flags, + }, + crate::Element::Reference( + ReferencePathType::SiblingReference(sibling_key), + _, + element_flags, + ) => grovedbg_types::Element::SiblingReference { + sibling_key, + element_flags, + }, + crate::Element::SumItem(value, element_flags) => grovedbg_types::Element::SumItem { + value, + element_flags, + }, + crate::Element::SumTree(root_key, sum, element_flags) => grovedbg_types::Element::Sumtree { + root_key, + sum, + element_flags, + }, + } +} + +fn node_to_update( + path: Path, + NodeDbg { + key, + value, + left_child, + right_child, + value_hash, + kv_digest_hash, + feature_type, + }: NodeDbg, +) -> Result { + // todo: GroveVersion::latest() to actual version + let grovedb_element = crate::Element::deserialize(&value, GroveVersion::latest())?; + + let element = element_to_grovedbg(grovedb_element); Ok(NodeUpdate { path, @@ -186,5 +427,13 @@ fn node_to_update( element, left_child, right_child, + feature_type: match feature_type { + TreeFeatureType::BasicMerkNode => grovedbg_types::TreeFeatureType::BasicMerkNode, + TreeFeatureType::SummedMerkNode(x) => { + grovedbg_types::TreeFeatureType::SummedMerkNode(x) + } + }, + value_hash, + kv_digest_hash, }) } diff --git a/grovedb/src/operations/proof/generate.rs b/grovedb/src/operations/proof/generate.rs index a5297eaf..6e814f67 100644 --- a/grovedb/src/operations/proof/generate.rs +++ b/grovedb/src/operations/proof/generate.rs @@ -97,7 +97,7 @@ impl GroveDb { } /// Generates a proof - fn prove_internal( + pub(crate) fn prove_internal( &self, path_query: &PathQuery, prove_options: Option, diff --git a/grovedbg-types/Cargo.toml b/grovedbg-types/Cargo.toml index a3a2878e..52beb675 100644 --- a/grovedbg-types/Cargo.toml +++ b/grovedbg-types/Cargo.toml @@ -9,3 +9,4 @@ repository = "https://github.com/dashpay/grovedb" [dependencies] serde = { version = "1.0.201", features = ["derive"] } +serde_with = { version = "3.9.0", features = ["base64"] } diff --git a/grovedbg-types/src/lib.rs b/grovedbg-types/src/lib.rs index dacc4255..0019db09 100644 --- a/grovedbg-types/src/lib.rs +++ b/grovedbg-types/src/lib.rs @@ -1,64 +1,242 @@ +use std::collections::BTreeMap; + use serde::{Deserialize, Serialize}; +use serde_with::{base64::Base64, serde_as}; pub type Key = Vec; pub type Path = Vec; pub type PathSegment = Vec; +#[serde_as] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct NodeFetchRequest { + #[serde_as(as = "Vec")] pub path: Path, + #[serde_as(as = "Base64")] pub key: Key, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct RootFetchRequest; +#[serde_as] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct NodeUpdate { + #[serde_as(as = "Option")] pub left_child: Option, + #[serde_as(as = "Option")] pub right_child: Option, + #[serde_as(as = "Vec")] pub path: Path, + #[serde_as(as = "Base64")] pub key: Key, pub element: Element, + pub feature_type: TreeFeatureType, + #[serde_as(as = "Base64")] + pub value_hash: CryptoHash, + #[serde_as(as = "Base64")] + pub kv_digest_hash: CryptoHash, } +#[serde_as] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub enum Element { Subtree { + #[serde_as(as = "Option")] root_key: Option, + #[serde_as(as = "Option")] + element_flags: Option>, }, Sumtree { + #[serde_as(as = "Option")] root_key: Option, sum: i64, + #[serde_as(as = "Option")] + element_flags: Option>, }, Item { + #[serde_as(as = "Base64")] value: Vec, + #[serde_as(as = "Option")] + element_flags: Option>, }, SumItem { value: i64, + #[serde_as(as = "Option")] + element_flags: Option>, }, AbsolutePathReference { + #[serde_as(as = "Vec")] path: Path, + #[serde_as(as = "Option")] + element_flags: Option>, }, UpstreamRootHeightReference { n_keep: u32, + #[serde_as(as = "Vec")] path_append: Vec, + #[serde_as(as = "Option")] + element_flags: Option>, }, UpstreamRootHeightWithParentPathAdditionReference { n_keep: u32, + #[serde_as(as = "Vec")] path_append: Vec, + #[serde_as(as = "Option")] + element_flags: Option>, }, UpstreamFromElementHeightReference { n_remove: u32, + #[serde_as(as = "Vec")] path_append: Vec, + #[serde_as(as = "Option")] + element_flags: Option>, }, CousinReference { + #[serde_as(as = "Base64")] swap_parent: PathSegment, + #[serde_as(as = "Option")] + element_flags: Option>, }, RemovedCousinReference { + #[serde_as(as = "Vec")] swap_parent: Vec, + #[serde_as(as = "Option")] + element_flags: Option>, }, SiblingReference { + #[serde_as(as = "Base64")] sibling_key: Key, + #[serde_as(as = "Option")] + element_flags: Option>, + }, +} + +#[serde_as] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct PathQuery { + #[serde_as(as = "Vec")] + pub path: Path, + pub query: SizedQuery, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct SizedQuery { + pub query: Query, + pub limit: Option, + pub offset: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct Query { + pub items: Vec, + pub default_subquery_branch: SubqueryBranch, + pub conditional_subquery_branches: Vec<(QueryItem, SubqueryBranch)>, + pub left_to_right: bool, +} + +#[serde_as] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub enum QueryItem { + Key(#[serde_as(as = "Base64")] Vec), + Range { + #[serde_as(as = "Base64")] + start: Key, + #[serde_as(as = "Base64")] + end: Key, }, + RangeInclusive { + #[serde_as(as = "Base64")] + start: Key, + #[serde_as(as = "Base64")] + end: Key, + }, + RangeFull, + RangeFrom(#[serde_as(as = "Base64")] Key), + RangeTo(#[serde_as(as = "Base64")] Key), + RangeToInclusive(#[serde_as(as = "Base64")] Key), + RangeAfter(#[serde_as(as = "Base64")] Key), + RangeAfterTo { + #[serde_as(as = "Base64")] + after: Key, + #[serde_as(as = "Base64")] + to: Key, + }, + RangeAfterToInclusive { + #[serde_as(as = "Base64")] + after: Key, + #[serde_as(as = "Base64")] + to: Key, + }, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct SubqueryBranch { + pub subquery_path: Option>, + pub subquery: Option>, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct Proof { + pub root_layer: ProofLayer, + pub prove_options: ProveOptions, +} + +#[serde_as] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct ProofLayer { + pub merk_proof: Vec, + #[serde_as(as = "BTreeMap")] + pub lower_layers: BTreeMap, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub enum MerkProofOp { + Push(MerkProofNode), + PushInverted(MerkProofNode), + Parent, + Child, + ParentInverted, + ChildInverted, +} + +pub type CryptoHash = [u8; 32]; + +#[serde_as] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub enum MerkProofNode { + Hash(#[serde_as(as = "Base64")] CryptoHash), + KVHash(#[serde_as(as = "Base64")] CryptoHash), + KVDigest( + #[serde_as(as = "Base64")] Key, + #[serde_as(as = "Base64")] CryptoHash, + ), + KV(#[serde_as(as = "Base64")] Key, Element), + KVValueHash( + #[serde_as(as = "Base64")] Key, + Element, + #[serde_as(as = "Base64")] CryptoHash, + ), + KVValueHashFeatureType( + #[serde_as(as = "Base64")] Key, + Element, + #[serde_as(as = "Base64")] CryptoHash, + TreeFeatureType, + ), + KVRefValueHash( + #[serde_as(as = "Base64")] Key, + Element, + #[serde_as(as = "Base64")] CryptoHash, + ), +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub enum TreeFeatureType { + BasicMerkNode, + SummedMerkNode(i64), +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct ProveOptions { + pub decrease_limit_on_empty_sub_query_result: bool, } diff --git a/merk/src/debugger.rs b/merk/src/debugger.rs index c5d322c0..c806351d 100644 --- a/merk/src/debugger.rs +++ b/merk/src/debugger.rs @@ -4,7 +4,7 @@ use grovedb_costs::CostsExt; use grovedb_storage::StorageContext; use grovedb_version::version::GroveVersion; -use crate::{tree::kv::ValueDefinedCostType, Error, Merk}; +use crate::{tree::kv::ValueDefinedCostType, CryptoHash, Error, Merk, TreeFeatureType}; impl<'a, S: StorageContext<'a>> Merk { pub fn get_node_dbg(&self, key: &[u8]) -> Result, Error> { @@ -16,6 +16,9 @@ impl<'a, S: StorageContext<'a>> Merk { value: tree.inner.value_as_slice().to_owned(), left_child: tree.link(true).map(|link| link.key().to_owned()), right_child: tree.link(false).map(|link| link.key().to_owned()), + value_hash: *tree.inner.kv.value_hash(), + kv_digest_hash: *tree.inner.kv.hash(), + feature_type: tree.inner.kv.feature_type(), } .wrap_with_cost(Default::default()) }, @@ -32,6 +35,9 @@ impl<'a, S: StorageContext<'a>> Merk { value: tree.inner.value_as_slice().to_owned(), left_child: tree.link(true).map(|link| link.key().to_owned()), right_child: tree.link(false).map(|link| link.key().to_owned()), + value_hash: *tree.inner.kv.value_hash(), + kv_digest_hash: *tree.inner.kv.hash(), + feature_type: tree.inner.kv.feature_type(), }) })) } @@ -43,4 +49,7 @@ pub struct NodeDbg { pub value: Vec, pub left_child: Option>, pub right_child: Option>, + pub value_hash: CryptoHash, + pub kv_digest_hash: CryptoHash, + pub feature_type: TreeFeatureType, } diff --git a/merk/src/tree/kv.rs b/merk/src/tree/kv.rs index b10733fc..4fd3f7f7 100644 --- a/merk/src/tree/kv.rs +++ b/merk/src/tree/kv.rs @@ -252,6 +252,10 @@ impl KV { &self.hash } + pub fn feature_type(&self) -> TreeFeatureType { + self.feature_type + } + /// Consumes the `KV` and returns its key without allocating or cloning. #[inline] pub fn take_key(self) -> Vec {