diff --git a/Cargo.lock b/Cargo.lock index 1dc1dd197b..f0343c58fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1394,6 +1394,7 @@ version = "0.75.0" dependencies = [ "anyhow", "async-trait", + "base64 0.21.7", "borsh", "futures", "hex", diff --git a/crates/bin/pcli/src/command/query.rs b/crates/bin/pcli/src/command/query.rs index b79fe5a933..8fe1cfe66f 100644 --- a/crates/bin/pcli/src/command/query.rs +++ b/crates/bin/pcli/src/command/query.rs @@ -11,12 +11,14 @@ mod tx; mod validator; use auction::AuctionCmd; +use base64::prelude::*; use chain::ChainCmd; use colored_json::ToColoredJson; use community_pool::CommunityPoolCmd; use dex::DexCmd; use governance::GovernanceCmd; use ibc_query::IbcCmd; +use penumbra_proto::cnidarium::v1::non_verifiable_key_value_request::Key as NVKey; use shielded_pool::ShieldedPool; use tx::Tx; pub(super) use validator::ValidatorCmd; @@ -40,7 +42,18 @@ pub enum QueryCmd { /// Queries an arbitrary key. Key { /// The key to query. + /// + /// When querying the JMT, keys are plain string values. + /// + /// When querying nonverifiable storage, keys should be base64-encoded strings. key: String, + /// The storage backend to query. + /// + /// Valid arguments are "jmt" and "nonverifiable". + /// + /// Defaults to the JMT. + #[clap(long, default_value = "jmt")] + storage_backend: String, }, /// Queries shielded pool data. #[clap(subcommand)] @@ -145,7 +158,7 @@ impl QueryCmd { return Ok(()); } - let key = match self { + let (key, storage_backend) = match self { QueryCmd::Tx(_) | QueryCmd::Chain(_) | QueryCmd::Validator(_) @@ -157,28 +170,61 @@ impl QueryCmd { | QueryCmd::Ibc(_) => { unreachable!("query handled in guard"); } - QueryCmd::ShieldedPool(p) => p.key().clone(), - QueryCmd::Key { key } => key.clone(), + QueryCmd::ShieldedPool(p) => (p.key().clone(), "jmt".to_string()), + QueryCmd::Key { + key, + storage_backend, + } => (key.clone(), storage_backend.clone()), }; use penumbra_proto::cnidarium::v1::query_service_client::QueryServiceClient; let mut client = QueryServiceClient::new(app.pd_channel().await?); - let req = penumbra_proto::cnidarium::v1::KeyValueRequest { - key: key.clone(), - ..Default::default() - }; + // Using an enum in the clap arguments was annoying; this is workable: + match storage_backend.as_str() { + "nonverifiable" => { + let key_bytes = BASE64_STANDARD + .decode(&key) + .map_err(|e| anyhow::anyhow!(format!("invalid base64: {}", e)))?; - tracing::debug!(?req); + let req = penumbra_proto::cnidarium::v1::NonVerifiableKeyValueRequest { + key: Some(NVKey { inner: key_bytes }), + ..Default::default() + }; - let value = client - .key_value(req) - .await? - .into_inner() - .value - .context(format!("key not found! key={}", key))?; + tracing::debug!(?req); + + let value = client + .non_verifiable_key_value(req) + .await? + .into_inner() + .value + .context(format!("key not found! key={}", key))?; + + self.display_value(&value.value)?; + } + // Default to JMT + "jmt" | _ => { + let req = penumbra_proto::cnidarium::v1::KeyValueRequest { + key: key.clone(), + // Command-line queries don't have a reason to include proofs as of now. + proof: false, + ..Default::default() + }; + + tracing::debug!(?req); + + let value = client + .key_value(req) + .await? + .into_inner() + .value + .context(format!("key not found! key={}", key))?; + + self.display_value(&value.value)?; + } + }; - self.display_value(&value.value)?; Ok(()) } diff --git a/crates/cnidarium/Cargo.toml b/crates/cnidarium/Cargo.toml index 9e7a9bfbbe..bd039898d7 100644 --- a/crates/cnidarium/Cargo.toml +++ b/crates/cnidarium/Cargo.toml @@ -12,6 +12,7 @@ rpc = ["dep:tonic", "dep:prost", "dep:serde", "dep:pbjson", "dep:ibc-proto"] [dependencies] anyhow = {workspace = true} async-trait = {workspace = true} +base64 = {workspace = true} borsh = { version = "1.3.0" , features = ["derive", "de_strict_order"]} futures = {workspace = true} hex = {workspace = true} diff --git a/crates/cnidarium/src/gen/penumbra.cnidarium.v1.rs b/crates/cnidarium/src/gen/penumbra.cnidarium.v1.rs index 590f79e139..7a04181967 100644 --- a/crates/cnidarium/src/gen/penumbra.cnidarium.v1.rs +++ b/crates/cnidarium/src/gen/penumbra.cnidarium.v1.rs @@ -1,4 +1,69 @@ -/// Performs a key-value query, either by key or by key hash. +/// Performs a key-value query against the nonverifiable storage, +/// using a byte-encoded key. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct NonVerifiableKeyValueRequest { + #[prost(message, optional, tag = "1")] + pub key: ::core::option::Option, +} +/// Nested message and enum types in `NonVerifiableKeyValueRequest`. +pub mod non_verifiable_key_value_request { + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Key { + #[prost(bytes = "vec", tag = "1")] + pub inner: ::prost::alloc::vec::Vec, + } + impl ::prost::Name for Key { + const NAME: &'static str = "Key"; + const PACKAGE: &'static str = "penumbra.cnidarium.v1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!( + "penumbra.cnidarium.v1.NonVerifiableKeyValueRequest.{}", Self::NAME + ) + } + } +} +impl ::prost::Name for NonVerifiableKeyValueRequest { + const NAME: &'static str = "NonVerifiableKeyValueRequest"; + const PACKAGE: &'static str = "penumbra.cnidarium.v1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!("penumbra.cnidarium.v1.{}", Self::NAME) + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct NonVerifiableKeyValueResponse { + /// The value corresponding to the specified key, if it was found. + #[prost(message, optional, tag = "1")] + pub value: ::core::option::Option, +} +/// Nested message and enum types in `NonVerifiableKeyValueResponse`. +pub mod non_verifiable_key_value_response { + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Value { + #[prost(bytes = "vec", tag = "1")] + pub value: ::prost::alloc::vec::Vec, + } + impl ::prost::Name for Value { + const NAME: &'static str = "Value"; + const PACKAGE: &'static str = "penumbra.cnidarium.v1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!( + "penumbra.cnidarium.v1.NonVerifiableKeyValueResponse.{}", Self::NAME + ) + } + } +} +impl ::prost::Name for NonVerifiableKeyValueResponse { + const NAME: &'static str = "NonVerifiableKeyValueResponse"; + const PACKAGE: &'static str = "penumbra.cnidarium.v1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!("penumbra.cnidarium.v1.{}", Self::NAME) + } +} +/// Performs a key-value query against the JMT, either by key or by key hash. /// /// Proofs are only supported by key. #[allow(clippy::derive_partial_eq_without_eq)] @@ -296,6 +361,38 @@ pub mod query_service_client { ); self.inner.unary(req, path, codec).await } + /// General-purpose key-value state query API, that can be used to query + /// arbitrary keys in the non-verifiable storage. + pub async fn non_verifiable_key_value( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/penumbra.cnidarium.v1.QueryService/NonVerifiableKeyValue", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new( + "penumbra.cnidarium.v1.QueryService", + "NonVerifiableKeyValue", + ), + ); + self.inner.unary(req, path, codec).await + } /// General-purpose prefixed key-value state query API, that can be used to query /// arbitrary prefixes in the JMT storage. pub async fn prefix_value( @@ -370,6 +467,15 @@ pub mod query_service_server { tonic::Response, tonic::Status, >; + /// General-purpose key-value state query API, that can be used to query + /// arbitrary keys in the non-verifiable storage. + async fn non_verifiable_key_value( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; /// Server streaming response type for the PrefixValue method. type PrefixValueStream: tonic::codegen::tokio_stream::Stream< Item = std::result::Result, @@ -522,6 +628,56 @@ pub mod query_service_server { }; Box::pin(fut) } + "/penumbra.cnidarium.v1.QueryService/NonVerifiableKeyValue" => { + #[allow(non_camel_case_types)] + struct NonVerifiableKeyValueSvc(pub Arc); + impl< + T: QueryService, + > tonic::server::UnaryService + for NonVerifiableKeyValueSvc { + type Response = super::NonVerifiableKeyValueResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::non_verifiable_key_value( + &inner, + request, + ) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = NonVerifiableKeyValueSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } "/penumbra.cnidarium.v1.QueryService/PrefixValue" => { #[allow(non_camel_case_types)] struct PrefixValueSvc(pub Arc); diff --git a/crates/cnidarium/src/gen/penumbra.cnidarium.v1.serde.rs b/crates/cnidarium/src/gen/penumbra.cnidarium.v1.serde.rs index 7a58283430..8751e80e6e 100644 --- a/crates/cnidarium/src/gen/penumbra.cnidarium.v1.serde.rs +++ b/crates/cnidarium/src/gen/penumbra.cnidarium.v1.serde.rs @@ -320,6 +320,392 @@ impl<'de> serde::Deserialize<'de> for key_value_response::Value { deserializer.deserialize_struct("penumbra.cnidarium.v1.KeyValueResponse.Value", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for NonVerifiableKeyValueRequest { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.key.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("penumbra.cnidarium.v1.NonVerifiableKeyValueRequest", len)?; + if let Some(v) = self.key.as_ref() { + struct_ser.serialize_field("key", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for NonVerifiableKeyValueRequest { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "key", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Key, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "key" => Ok(GeneratedField::Key), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = NonVerifiableKeyValueRequest; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct penumbra.cnidarium.v1.NonVerifiableKeyValueRequest") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut key__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Key => { + if key__.is_some() { + return Err(serde::de::Error::duplicate_field("key")); + } + key__ = map_.next_value()?; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(NonVerifiableKeyValueRequest { + key: key__, + }) + } + } + deserializer.deserialize_struct("penumbra.cnidarium.v1.NonVerifiableKeyValueRequest", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for non_verifiable_key_value_request::Key { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.inner.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("penumbra.cnidarium.v1.NonVerifiableKeyValueRequest.Key", len)?; + if !self.inner.is_empty() { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("inner", pbjson::private::base64::encode(&self.inner).as_str())?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for non_verifiable_key_value_request::Key { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "inner", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Inner, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "inner" => Ok(GeneratedField::Inner), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = non_verifiable_key_value_request::Key; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct penumbra.cnidarium.v1.NonVerifiableKeyValueRequest.Key") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut inner__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Inner => { + if inner__.is_some() { + return Err(serde::de::Error::duplicate_field("inner")); + } + inner__ = + Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) + ; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(non_verifiable_key_value_request::Key { + inner: inner__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("penumbra.cnidarium.v1.NonVerifiableKeyValueRequest.Key", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for NonVerifiableKeyValueResponse { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.value.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("penumbra.cnidarium.v1.NonVerifiableKeyValueResponse", len)?; + if let Some(v) = self.value.as_ref() { + struct_ser.serialize_field("value", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for NonVerifiableKeyValueResponse { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "value", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Value, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "value" => Ok(GeneratedField::Value), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = NonVerifiableKeyValueResponse; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct penumbra.cnidarium.v1.NonVerifiableKeyValueResponse") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut value__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Value => { + if value__.is_some() { + return Err(serde::de::Error::duplicate_field("value")); + } + value__ = map_.next_value()?; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(NonVerifiableKeyValueResponse { + value: value__, + }) + } + } + deserializer.deserialize_struct("penumbra.cnidarium.v1.NonVerifiableKeyValueResponse", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for non_verifiable_key_value_response::Value { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.value.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("penumbra.cnidarium.v1.NonVerifiableKeyValueResponse.Value", len)?; + if !self.value.is_empty() { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("value", pbjson::private::base64::encode(&self.value).as_str())?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for non_verifiable_key_value_response::Value { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "value", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Value, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "value" => Ok(GeneratedField::Value), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = non_verifiable_key_value_response::Value; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct penumbra.cnidarium.v1.NonVerifiableKeyValueResponse.Value") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut value__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Value => { + if value__.is_some() { + return Err(serde::de::Error::duplicate_field("value")); + } + value__ = + Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) + ; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(non_verifiable_key_value_response::Value { + value: value__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("penumbra.cnidarium.v1.NonVerifiableKeyValueResponse.Value", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for PrefixValueRequest { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result diff --git a/crates/cnidarium/src/gen/proto_descriptor.bin.no_lfs b/crates/cnidarium/src/gen/proto_descriptor.bin.no_lfs index d50e1bd881..cb5f8b9377 100644 Binary files a/crates/cnidarium/src/gen/proto_descriptor.bin.no_lfs and b/crates/cnidarium/src/gen/proto_descriptor.bin.no_lfs differ diff --git a/crates/cnidarium/src/rpc.rs b/crates/cnidarium/src/rpc.rs index 992fb35a38..cfb3ee3bee 100644 --- a/crates/cnidarium/src/rpc.rs +++ b/crates/cnidarium/src/rpc.rs @@ -23,9 +23,10 @@ use std::pin::Pin; use crate::read::StateRead; use crate::rpc::proto::v1::{ - key_value_response::Value, query_service_server::QueryService, watch_response as wr, - KeyValueRequest, KeyValueResponse, PrefixValueRequest, PrefixValueResponse, WatchRequest, - WatchResponse, + key_value_response::Value as JMTValue, non_verifiable_key_value_response::Value as NVValue, + query_service_server::QueryService, watch_response as wr, KeyValueRequest, KeyValueResponse, + NonVerifiableKeyValueRequest, NonVerifiableKeyValueResponse, PrefixValueRequest, + PrefixValueResponse, WatchRequest, WatchResponse, }; use futures::{StreamExt, TryStreamExt}; use regex::Regex; @@ -37,6 +38,29 @@ use crate::Storage; #[tonic::async_trait] impl QueryService for Server { + #[instrument(skip(self, request))] + async fn non_verifiable_key_value( + &self, + request: tonic::Request, + ) -> Result, Status> { + let state = self.storage.latest_snapshot(); + let request = request.into_inner(); + + if request.key.is_none() || request.key.as_ref().expect("key is Some").inner.is_empty() { + return Err(Status::invalid_argument("key is empty")); + } + + let key = request.key.expect("key is Some").inner; + let some_value = state + .nonverifiable_get_raw(&key) + .await + .map_err(|e| tonic::Status::internal(e.to_string()))?; + + Ok(tonic::Response::new(NonVerifiableKeyValueResponse { + value: some_value.map(|value| NVValue { value }), + })) + } + #[instrument(skip(self, request))] async fn key_value( &self, @@ -52,17 +76,32 @@ impl QueryService for Server { return Err(Status::invalid_argument("key is empty")); } - // TODO(erwan): Don't generate the proof if the request doesn't ask for it. Tracked in #2647. - let (some_value, proof) = state - .get_with_proof(request.key.into_bytes()) - .await - .map_err(|e| tonic::Status::internal(e.to_string()))?; + let (some_value, proof) = { + // Don't generate the proof if the request doesn't ask for it. + let (v, p) = if request.proof { + let (v, p) = state + .get_with_proof(request.key.into_bytes()) + .await + .map_err(|e| tonic::Status::internal(e.to_string()))?; + (v, Some(p)) + } else { + ( + state + .get_raw(&request.key) + .await + .map_err(|e| tonic::Status::internal(e.to_string()))?, + None, + ) + }; + (v, p) + }; Ok(tonic::Response::new(KeyValueResponse { - value: some_value.map(|value| Value { value }), + value: some_value.map(|value| JMTValue { value }), proof: if request.proof { Some(ibc_proto::ibc::core::commitment::v1::MerkleProof { proofs: proof + .expect("proof should be present") .proofs .into_iter() .map(|p| { diff --git a/crates/proto/src/gen/penumbra.cnidarium.v1.rs b/crates/proto/src/gen/penumbra.cnidarium.v1.rs index 590f79e139..7a04181967 100644 --- a/crates/proto/src/gen/penumbra.cnidarium.v1.rs +++ b/crates/proto/src/gen/penumbra.cnidarium.v1.rs @@ -1,4 +1,69 @@ -/// Performs a key-value query, either by key or by key hash. +/// Performs a key-value query against the nonverifiable storage, +/// using a byte-encoded key. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct NonVerifiableKeyValueRequest { + #[prost(message, optional, tag = "1")] + pub key: ::core::option::Option, +} +/// Nested message and enum types in `NonVerifiableKeyValueRequest`. +pub mod non_verifiable_key_value_request { + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Key { + #[prost(bytes = "vec", tag = "1")] + pub inner: ::prost::alloc::vec::Vec, + } + impl ::prost::Name for Key { + const NAME: &'static str = "Key"; + const PACKAGE: &'static str = "penumbra.cnidarium.v1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!( + "penumbra.cnidarium.v1.NonVerifiableKeyValueRequest.{}", Self::NAME + ) + } + } +} +impl ::prost::Name for NonVerifiableKeyValueRequest { + const NAME: &'static str = "NonVerifiableKeyValueRequest"; + const PACKAGE: &'static str = "penumbra.cnidarium.v1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!("penumbra.cnidarium.v1.{}", Self::NAME) + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct NonVerifiableKeyValueResponse { + /// The value corresponding to the specified key, if it was found. + #[prost(message, optional, tag = "1")] + pub value: ::core::option::Option, +} +/// Nested message and enum types in `NonVerifiableKeyValueResponse`. +pub mod non_verifiable_key_value_response { + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Value { + #[prost(bytes = "vec", tag = "1")] + pub value: ::prost::alloc::vec::Vec, + } + impl ::prost::Name for Value { + const NAME: &'static str = "Value"; + const PACKAGE: &'static str = "penumbra.cnidarium.v1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!( + "penumbra.cnidarium.v1.NonVerifiableKeyValueResponse.{}", Self::NAME + ) + } + } +} +impl ::prost::Name for NonVerifiableKeyValueResponse { + const NAME: &'static str = "NonVerifiableKeyValueResponse"; + const PACKAGE: &'static str = "penumbra.cnidarium.v1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!("penumbra.cnidarium.v1.{}", Self::NAME) + } +} +/// Performs a key-value query against the JMT, either by key or by key hash. /// /// Proofs are only supported by key. #[allow(clippy::derive_partial_eq_without_eq)] @@ -296,6 +361,38 @@ pub mod query_service_client { ); self.inner.unary(req, path, codec).await } + /// General-purpose key-value state query API, that can be used to query + /// arbitrary keys in the non-verifiable storage. + pub async fn non_verifiable_key_value( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/penumbra.cnidarium.v1.QueryService/NonVerifiableKeyValue", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new( + "penumbra.cnidarium.v1.QueryService", + "NonVerifiableKeyValue", + ), + ); + self.inner.unary(req, path, codec).await + } /// General-purpose prefixed key-value state query API, that can be used to query /// arbitrary prefixes in the JMT storage. pub async fn prefix_value( @@ -370,6 +467,15 @@ pub mod query_service_server { tonic::Response, tonic::Status, >; + /// General-purpose key-value state query API, that can be used to query + /// arbitrary keys in the non-verifiable storage. + async fn non_verifiable_key_value( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; /// Server streaming response type for the PrefixValue method. type PrefixValueStream: tonic::codegen::tokio_stream::Stream< Item = std::result::Result, @@ -522,6 +628,56 @@ pub mod query_service_server { }; Box::pin(fut) } + "/penumbra.cnidarium.v1.QueryService/NonVerifiableKeyValue" => { + #[allow(non_camel_case_types)] + struct NonVerifiableKeyValueSvc(pub Arc); + impl< + T: QueryService, + > tonic::server::UnaryService + for NonVerifiableKeyValueSvc { + type Response = super::NonVerifiableKeyValueResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::non_verifiable_key_value( + &inner, + request, + ) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = NonVerifiableKeyValueSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } "/penumbra.cnidarium.v1.QueryService/PrefixValue" => { #[allow(non_camel_case_types)] struct PrefixValueSvc(pub Arc); diff --git a/crates/proto/src/gen/penumbra.cnidarium.v1.serde.rs b/crates/proto/src/gen/penumbra.cnidarium.v1.serde.rs index 7a58283430..8751e80e6e 100644 --- a/crates/proto/src/gen/penumbra.cnidarium.v1.serde.rs +++ b/crates/proto/src/gen/penumbra.cnidarium.v1.serde.rs @@ -320,6 +320,392 @@ impl<'de> serde::Deserialize<'de> for key_value_response::Value { deserializer.deserialize_struct("penumbra.cnidarium.v1.KeyValueResponse.Value", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for NonVerifiableKeyValueRequest { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.key.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("penumbra.cnidarium.v1.NonVerifiableKeyValueRequest", len)?; + if let Some(v) = self.key.as_ref() { + struct_ser.serialize_field("key", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for NonVerifiableKeyValueRequest { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "key", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Key, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "key" => Ok(GeneratedField::Key), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = NonVerifiableKeyValueRequest; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct penumbra.cnidarium.v1.NonVerifiableKeyValueRequest") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut key__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Key => { + if key__.is_some() { + return Err(serde::de::Error::duplicate_field("key")); + } + key__ = map_.next_value()?; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(NonVerifiableKeyValueRequest { + key: key__, + }) + } + } + deserializer.deserialize_struct("penumbra.cnidarium.v1.NonVerifiableKeyValueRequest", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for non_verifiable_key_value_request::Key { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.inner.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("penumbra.cnidarium.v1.NonVerifiableKeyValueRequest.Key", len)?; + if !self.inner.is_empty() { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("inner", pbjson::private::base64::encode(&self.inner).as_str())?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for non_verifiable_key_value_request::Key { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "inner", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Inner, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "inner" => Ok(GeneratedField::Inner), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = non_verifiable_key_value_request::Key; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct penumbra.cnidarium.v1.NonVerifiableKeyValueRequest.Key") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut inner__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Inner => { + if inner__.is_some() { + return Err(serde::de::Error::duplicate_field("inner")); + } + inner__ = + Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) + ; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(non_verifiable_key_value_request::Key { + inner: inner__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("penumbra.cnidarium.v1.NonVerifiableKeyValueRequest.Key", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for NonVerifiableKeyValueResponse { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.value.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("penumbra.cnidarium.v1.NonVerifiableKeyValueResponse", len)?; + if let Some(v) = self.value.as_ref() { + struct_ser.serialize_field("value", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for NonVerifiableKeyValueResponse { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "value", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Value, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "value" => Ok(GeneratedField::Value), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = NonVerifiableKeyValueResponse; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct penumbra.cnidarium.v1.NonVerifiableKeyValueResponse") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut value__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Value => { + if value__.is_some() { + return Err(serde::de::Error::duplicate_field("value")); + } + value__ = map_.next_value()?; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(NonVerifiableKeyValueResponse { + value: value__, + }) + } + } + deserializer.deserialize_struct("penumbra.cnidarium.v1.NonVerifiableKeyValueResponse", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for non_verifiable_key_value_response::Value { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.value.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("penumbra.cnidarium.v1.NonVerifiableKeyValueResponse.Value", len)?; + if !self.value.is_empty() { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("value", pbjson::private::base64::encode(&self.value).as_str())?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for non_verifiable_key_value_response::Value { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "value", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Value, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "value" => Ok(GeneratedField::Value), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = non_verifiable_key_value_response::Value; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct penumbra.cnidarium.v1.NonVerifiableKeyValueResponse.Value") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut value__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Value => { + if value__.is_some() { + return Err(serde::de::Error::duplicate_field("value")); + } + value__ = + Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) + ; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(non_verifiable_key_value_response::Value { + value: value__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("penumbra.cnidarium.v1.NonVerifiableKeyValueResponse.Value", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for PrefixValueRequest { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result diff --git a/crates/proto/src/gen/proto_descriptor.bin.no_lfs b/crates/proto/src/gen/proto_descriptor.bin.no_lfs index e7a8642ba9..93fdbd3571 100644 Binary files a/crates/proto/src/gen/proto_descriptor.bin.no_lfs and b/crates/proto/src/gen/proto_descriptor.bin.no_lfs differ diff --git a/crates/view/src/worker.rs b/crates/view/src/worker.rs index 81d3a6f34f..887ee79a97 100644 --- a/crates/view/src/worker.rs +++ b/crates/view/src/worker.rs @@ -502,6 +502,7 @@ async fn sct_divergence_check( let value = client .key_value(penumbra_proto::cnidarium::v1::KeyValueRequest { key: sct_state_key::tree::anchor_by_height(height), + proof: false, ..Default::default() }) .await? diff --git a/proto/penumbra/penumbra/cnidarium/v1/cnidarium.proto b/proto/penumbra/penumbra/cnidarium/v1/cnidarium.proto index b313003731..87ecd84fb3 100644 --- a/proto/penumbra/penumbra/cnidarium/v1/cnidarium.proto +++ b/proto/penumbra/penumbra/cnidarium/v1/cnidarium.proto @@ -9,6 +9,10 @@ service QueryService { // arbitrary keys in the JMT storage. rpc KeyValue(KeyValueRequest) returns (KeyValueResponse); + // General-purpose key-value state query API, that can be used to query + // arbitrary keys in the non-verifiable storage. + rpc NonVerifiableKeyValue(NonVerifiableKeyValueRequest) returns (NonVerifiableKeyValueResponse); + // General-purpose prefixed key-value state query API, that can be used to query // arbitrary prefixes in the JMT storage. rpc PrefixValue(PrefixValueRequest) returns (stream PrefixValueResponse); @@ -17,7 +21,25 @@ service QueryService { rpc Watch(WatchRequest) returns (stream WatchResponse); } -// Performs a key-value query, either by key or by key hash. +// Performs a key-value query against the nonverifiable storage, +// using a byte-encoded key. +message NonVerifiableKeyValueRequest { + message Key { + bytes inner = 1; + } + + Key key = 1; +} + +message NonVerifiableKeyValueResponse { + message Value { + bytes value = 1; + } + // The value corresponding to the specified key, if it was found. + Value value = 1; +} + +// Performs a key-value query against the JMT, either by key or by key hash. // // Proofs are only supported by key. message KeyValueRequest {