diff --git a/Cargo.lock b/Cargo.lock index e06f30b73e..7d960e0b37 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4907,6 +4907,7 @@ dependencies = [ "penumbra-shielded-pool", "penumbra-stake", "penumbra-tct", + "penumbra-tendermint-proxy", "penumbra-transaction", "penumbra-txhash", "prost", @@ -4924,6 +4925,8 @@ dependencies = [ "tendermint-proto", "tokio", "tonic", + "tonic-reflection", + "tonic-web", "tracing", "tracing-subscriber 0.3.18", ] diff --git a/crates/bin/pd/src/main.rs b/crates/bin/pd/src/main.rs index e823c1b419..b4588ab3f2 100644 --- a/crates/bin/pd/src/main.rs +++ b/crates/bin/pd/src/main.rs @@ -9,9 +9,6 @@ use metrics_util::layers::Stack; use anyhow::Context; use cnidarium::{StateDelta, Storage}; -use ibc_proto::ibc::core::channel::v1::query_server::QueryServer as ChannelQueryServer; -use ibc_proto::ibc::core::client::v1::query_server::QueryServer as ClientQueryServer; -use ibc_proto::ibc::core::connection::v1::query_server::QueryServer as ConnectionQueryServer; use metrics_exporter_prometheus::PrometheusBuilder; use pd::{ cli::{Opt, RootCommand, TestnetCommand}, @@ -24,8 +21,6 @@ use pd::{ }, }; use penumbra_app::{PenumbraHost, SUBSTORE_PREFIXES}; -use penumbra_proto::core::component::dex::v1alpha1::simulation_service_server::SimulationServiceServer; -use penumbra_proto::util::tendermint_proxy::v1alpha1::tendermint_proxy_service_server::TendermintProxyServiceServer; use penumbra_tendermint_proxy::TendermintProxy; use penumbra_tower_trace::remote_addr; use rand::Rng; @@ -153,7 +148,6 @@ async fn main() -> anyhow::Result<()> { async move { pd::Mempool::new(storage.clone(), queue).await?.run().await } })); let info = pd::Info::new(storage.clone()); - let tm_proxy = TendermintProxy::new(cometbft_addr); let snapshot = pd::Snapshot {}; let abci_server = tokio::task::Builder::new() @@ -170,48 +164,21 @@ async fn main() -> anyhow::Result<()> { ) .expect("failed to spawn abci server"); - let ibc = penumbra_ibc::component::rpc::IbcQuery::::new(storage.clone()); - - // TODO: Once we migrate to Tonic 0.10.0, we'll be able to use the - // `Routes` structure to have each component define a method that - // returns a `Routes` with all of its query services bundled inside. - // - // This means we won't have to import all this shit and recite every - // single service -- we can e.g., have the app crate assemble all of - // its components' query services into a single `Routes` and then - // just add that to the gRPC server. - - use cnidarium::rpc::proto::v1alpha1::query_service_server::QueryServiceServer as StorageQueryServiceServer; - use penumbra_proto::core::{ - app::v1alpha1::query_service_server::QueryServiceServer as AppQueryServiceServer, - component::{ - chain::v1alpha1::query_service_server::QueryServiceServer as ChainQueryServiceServer, - compact_block::v1alpha1::query_service_server::QueryServiceServer as CompactBlockQueryServiceServer, - dex::v1alpha1::query_service_server::QueryServiceServer as DexQueryServiceServer, - governance::v1alpha1::query_service_server::QueryServiceServer as GovernanceQueryServiceServer, - sct::v1alpha1::query_service_server::QueryServiceServer as SctQueryServiceServer, - shielded_pool::v1alpha1::query_service_server::QueryServiceServer as ShieldedPoolQueryServiceServer, - stake::v1alpha1::query_service_server::QueryServiceServer as StakeQueryServiceServer, - }, - }; - use tonic_web::enable as we; - - use cnidarium::rpc::Server as StorageServer; - use penumbra_app::rpc::Server as AppServer; - use penumbra_chain::component::rpc::Server as ChainServer; - use penumbra_compact_block::component::rpc::Server as CompactBlockServer; - use penumbra_dex::component::rpc::Server as DexServer; - use penumbra_governance::component::rpc::Server as GovernanceServer; - use penumbra_sct::component::rpc::Server as SctServer; - use penumbra_shielded_pool::component::rpc::Server as ShieldedPoolServer; - use penumbra_stake::component::rpc::Server as StakeServer; - // Set rather permissive CORS headers for pd's gRPC: the service // should be accessible from arbitrary web contexts, such as localhost, // or any FQDN that wants to reference its data. let cors_layer = CorsLayer::permissive(); - let mut grpc_server = Server::builder() + let ibc = penumbra_ibc::component::rpc::IbcQuery::::new(storage.clone()); + let tendermint_proxy = TendermintProxy::new(cometbft_addr); + let routes = penumbra_app::rpc::Routes { + storage, + ibc, + tendermint_proxy, + enable_expensive_rpc, + } + .build()?; + let grpc_server = Server::builder() .trace_fn(|req| match remote_addr(req) { Some(remote_addr) => { tracing::error_span!("grpc", ?remote_addr) @@ -234,47 +201,7 @@ async fn main() -> anyhow::Result<()> { // new blocks. // .timeout(std::time::Duration::from_secs(7)) // Wrap each of the gRPC services in a tonic-web proxy: - .add_service(we(StorageQueryServiceServer::new(StorageServer::new( - storage.clone(), - )))) - .add_service(we(AppQueryServiceServer::new(AppServer::new( - storage.clone(), - )))) - .add_service(we(ChainQueryServiceServer::new(ChainServer::new( - storage.clone(), - )))) - .add_service(we(CompactBlockQueryServiceServer::new( - CompactBlockServer::new(storage.clone()), - ))) - .add_service(we(DexQueryServiceServer::new(DexServer::new( - storage.clone(), - )))) - .add_service(we(GovernanceQueryServiceServer::new( - GovernanceServer::new(storage.clone()), - ))) - .add_service(we(SctQueryServiceServer::new(SctServer::new( - storage.clone(), - )))) - .add_service(we(ShieldedPoolQueryServiceServer::new( - ShieldedPoolServer::new(storage.clone()), - ))) - .add_service(we(StakeQueryServiceServer::new(StakeServer::new( - storage.clone(), - )))) - .add_service(we(ClientQueryServer::new(ibc.clone()))) - .add_service(we(ChannelQueryServer::new(ibc.clone()))) - .add_service(we(ConnectionQueryServer::new(ibc.clone()))) - .add_service(we(TendermintProxyServiceServer::new(tm_proxy.clone()))) - .add_service(we(tonic_reflection::server::Builder::configure() - .register_encoded_file_descriptor_set(penumbra_proto::FILE_DESCRIPTOR_SET) - .build() - .with_context(|| "could not configure grpc reflection service")?)); - - if enable_expensive_rpc { - grpc_server = grpc_server.add_service(we(SimulationServiceServer::new( - DexServer::new(storage.clone()), - ))); - } + .add_routes(routes); // Now we drop down a layer of abstraction, from tonic to axum. // diff --git a/crates/core/app/Cargo.toml b/crates/core/app/Cargo.toml index bd834ddb16..b2173630a9 100644 --- a/crates/core/app/Cargo.toml +++ b/crates/core/app/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" [dependencies] # Workspace dependencies -cnidarium = { path = "../../cnidarium" } +cnidarium = { path = "../../cnidarium", features = ["rpc"] } cnidarium-component = { path = "../../cnidarium-component" } penumbra-proto = { path = "../../proto", features = ["cnidarium"] } penumbra-tct = { path = "../../crypto/tct" } @@ -27,10 +27,11 @@ penumbra-fee = { path = "../component/fee" } penumbra-funding = { path = "../component/funding" } penumbra-community-pool = { path = "../component/community-pool" } penumbra-dex = { path = "../component/dex" } -penumbra-ibc = { path = "../component/ibc", features = ["component"] } +penumbra-ibc = { path = "../component/ibc", features = ["component", "rpc"] } penumbra-distributions = { path = "../component/distributions" } penumbra-compact-block = { path = "../component/compact-block" } penumbra-transaction = { path = "../transaction", features = ["parallel"] } +penumbra-tendermint-proxy = { path = "../../util/tendermint-proxy" } # External dependencies decaf377 = { version = "0.5" } @@ -39,6 +40,8 @@ jmt = "0.9" tokio = { version = "1.21.1", features = ["full", "tracing"] } async-trait = "0.1.52" tonic = "0.10" +tonic-web = "0.10.0" +tonic-reflection = "0.10.0" futures = "0.3" anyhow = "1" tracing = "0.1" diff --git a/crates/core/app/src/rpc.rs b/crates/core/app/src/rpc.rs index 757badf5bc..5d3522fd1d 100644 --- a/crates/core/app/src/rpc.rs +++ b/crates/core/app/src/rpc.rs @@ -1,16 +1,119 @@ -use cnidarium::Storage; -use penumbra_chain::component::StateReadExt as _; -use penumbra_proto::core::app::v1alpha1::{ - query_service_server::QueryService, AppParametersRequest, AppParametersResponse, - TransactionsByHeightRequest, TransactionsByHeightResponse, +use { + crate::{app::StateReadExt as _, PenumbraHost}, + anyhow::Context, + cnidarium::{ + rpc::{ + proto::v1alpha1::query_service_server::QueryServiceServer as StorageQueryServiceServer, + Server as StorageServer, + }, + Storage, + }, + ibc_proto::ibc::core::{ + channel::v1::query_server::QueryServer as ChannelQueryServer, + client::v1::query_server::QueryServer as ClientQueryServer, + connection::v1::query_server::QueryServer as ConnectionQueryServer, + }, + penumbra_chain::component::{rpc::Server as ChainServer, StateReadExt as _}, + penumbra_compact_block::component::rpc::Server as CompactBlockServer, + penumbra_dex::component::rpc::Server as DexServer, + penumbra_governance::component::rpc::Server as GovernanceServer, + penumbra_ibc::component::rpc::IbcQuery, + penumbra_proto::core::app::v1alpha1::{ + query_service_server::QueryService, AppParametersRequest, AppParametersResponse, + TransactionsByHeightRequest, TransactionsByHeightResponse, + }, + penumbra_proto::core::component::dex::v1alpha1::simulation_service_server::SimulationServiceServer, + penumbra_proto::core::{ + app::v1alpha1::query_service_server::QueryServiceServer as AppQueryServiceServer, + component::{ + chain::v1alpha1::query_service_server::QueryServiceServer as ChainQueryServiceServer, + compact_block::v1alpha1::query_service_server::QueryServiceServer as CompactBlockQueryServiceServer, + dex::v1alpha1::query_service_server::QueryServiceServer as DexQueryServiceServer, + governance::v1alpha1::query_service_server::QueryServiceServer as GovernanceQueryServiceServer, + sct::v1alpha1::query_service_server::QueryServiceServer as SctQueryServiceServer, + shielded_pool::v1alpha1::query_service_server::QueryServiceServer as ShieldedPoolQueryServiceServer, + stake::v1alpha1::query_service_server::QueryServiceServer as StakeQueryServiceServer, + }, + }, + penumbra_proto::util::tendermint_proxy::v1alpha1::tendermint_proxy_service_server::TendermintProxyServiceServer, + penumbra_sct::component::rpc::Server as SctServer, + penumbra_shielded_pool::component::rpc::Server as ShieldedPoolServer, + penumbra_stake::component::rpc::Server as StakeServer, + penumbra_tendermint_proxy::TendermintProxy, + tonic::Status, + tonic_web::enable as we, + tracing::instrument, }; -use tonic::Status; -use tracing::instrument; -use crate::app::StateReadExt as _; +/// Used to construct a [`tonic::transport::server::Routes`] router. +/// +/// Use [`Routes::build()`] to try to construct a set of tonic routes. +pub struct Routes { + pub storage: Storage, + pub ibc: IbcQuery, + pub tendermint_proxy: TendermintProxy, + pub enable_expensive_rpc: bool, +} + +impl Routes { + /// Returns a [`Routes`] that can be used to service requests. + pub fn build(self) -> Result { + let Self { + storage, + ibc, + tendermint_proxy, + enable_expensive_rpc, + } = self; + + let routes = tonic::transport::server::Routes::new(we(StorageQueryServiceServer::new( + StorageServer::new(storage.clone()), + ))) + .add_service(AppQueryServiceServer::new(self::Server::new( + storage.clone(), + ))) + .add_service(we(ChainQueryServiceServer::new(ChainServer::new( + storage.clone(), + )))) + .add_service(we(CompactBlockQueryServiceServer::new( + CompactBlockServer::new(storage.clone()), + ))) + .add_service(we(DexQueryServiceServer::new(DexServer::new( + storage.clone(), + )))) + .add_service(we(GovernanceQueryServiceServer::new( + GovernanceServer::new(storage.clone()), + ))) + .add_service(we(SctQueryServiceServer::new(SctServer::new( + storage.clone(), + )))) + .add_service(we(ShieldedPoolQueryServiceServer::new( + ShieldedPoolServer::new(storage.clone()), + ))) + .add_service(we(StakeQueryServiceServer::new(StakeServer::new( + storage.clone(), + )))) + .add_service(we(ClientQueryServer::new(ibc.clone()))) + .add_service(we(ChannelQueryServer::new(ibc.clone()))) + .add_service(we(ConnectionQueryServer::new(ibc.clone()))) + .add_service(we(TendermintProxyServiceServer::new(tendermint_proxy))) + .add_service(we(tonic_reflection::server::Builder::configure() + .register_encoded_file_descriptor_set(penumbra_proto::FILE_DESCRIPTOR_SET) + .build() + .with_context(|| "could not configure grpc reflection service")?)); + + let routes = if enable_expensive_rpc { + routes.add_service(we(SimulationServiceServer::new(DexServer::new( + storage.clone(), + )))) + } else { + routes + }; + + Ok(routes) + } +} -// TODO: Hide this and only expose a Router? -pub struct Server { +struct Server { storage: Storage, }