Skip to content

Commit

Permalink
feat(grpc): Add initial Getinfo grpc (#8178)
Browse files Browse the repository at this point in the history
* add `zebra-grpc` crate

* add missing fields

* convert to a lib

* add zebra-scan and tonic as depenency

* add a getinfo grpc

* remove zebra-scanner dependency

* Adds scan_service field to scanner grpc server

* remove dependency

* test launching the grpc server from the zebra-scan crate (not building)

* fix async issue

* fixes build issues

* add binary for manual testing

* try fix try run

---------

Co-authored-by: Arya <aryasolhi@gmail.com>
  • Loading branch information
oxarbitrage and arya2 authored Jan 26, 2024
1 parent c08ad45 commit 78d33f3
Show file tree
Hide file tree
Showing 15 changed files with 256 additions and 21 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/scripts/release-crates-dry-run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ fi

# Release process
# Ensure to have an extra `--no-confirm` argument for non-interactive testing.
cargo release version --verbose --execute --no-confirm --allow-branch '*' --workspace --exclude zebrad beta
cargo release version --verbose --execute --no-confirm --allow-branch '*' --workspace --exclude zebrad --exclude zebra-scan --exclude zebra-grpc beta
# TODO: `zebra-scan` and `zebra-grpc` has to be updated with exact versions, we are skipping them by now.
cargo release version --verbose --execute --no-confirm --allow-branch '*' --package zebrad patch
cargo release replace --verbose --execute --no-confirm --allow-branch '*' --package zebrad
cargo release commit --verbose --execute --no-confirm --allow-branch '*'
Expand Down
10 changes: 9 additions & 1 deletion Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5766,8 +5766,15 @@ dependencies = [
name = "zebra-grpc"
version = "0.1.0-alpha.1"
dependencies = [
"color-eyre",
"futures-util",
"prost",
"tokio",
"tonic",
"zebra-scan",
"tonic-build",
"tower",
"zcash_primitives",
"zebra-node-services",
]

[[package]]
Expand Down Expand Up @@ -5880,6 +5887,7 @@ dependencies = [
"zcash_note_encryption",
"zcash_primitives",
"zebra-chain",
"zebra-grpc",
"zebra-node-services",
"zebra-state",
"zebra-test",
Expand Down
12 changes: 11 additions & 1 deletion zebra-grpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ categories = ["cryptography::cryptocurrencies"]

[dependencies]

futures-util = "0.3.28"
tonic = "0.10.2"
prost = "0.12.3"
tokio = { version = "1.35.1", features = ["macros", "rt-multi-thread"] }
tower = { version = "0.4.13", features = ["util", "buffer"] }
color-eyre = "0.6.2"

zebra-scan = { path = "../zebra-scan", version = "0.1.0-alpha.1" }
zcash_primitives = { version = "0.13.0-rc.1" }

zebra-node-services = { path = "../zebra-node-services", version = "1.0.0-beta.34" }

[build-dependencies]
tonic-build = "0.10.2"
6 changes: 6 additions & 0 deletions zebra-grpc/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
//! Compile proto files
fn main() -> Result<(), Box<dyn std::error::Error>> {
tonic_build::compile_protos("proto/scanner.proto")?;
Ok(())
}
16 changes: 16 additions & 0 deletions zebra-grpc/proto/scanner.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
syntax = "proto3";
package scanner;

// Empty is for gRPCs that take no arguments, currently only GetInfo.
message Empty {}

service Scanner {
// Get information about the scanner service.
rpc GetInfo (Empty) returns (InfoReply);
}

// A response to a GetInfo call.
message InfoReply {
// The minimum sapling height allowed.
uint32 min_sapling_birthday_height = 1;
}
2 changes: 2 additions & 0 deletions zebra-grpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@
#![doc(html_favicon_url = "https://zfnd.org/wp-content/uploads/2022/03/zebra-favicon-128.png")]
#![doc(html_logo_url = "https://zfnd.org/wp-content/uploads/2022/03/zebra-icon.png")]
#![doc(html_root_url = "https://docs.rs/zebra_grpc")]

pub mod server;
91 changes: 91 additions & 0 deletions zebra-grpc/src/server.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
//! The gRPC server implementation
use futures_util::future::TryFutureExt;
use tonic::{transport::Server, Response, Status};
use tower::ServiceExt;

use scanner::scanner_server::{Scanner, ScannerServer};
use scanner::{Empty, InfoReply};

use zebra_node_services::scan_service::{
request::Request as ScanServiceRequest, response::Response as ScanServiceResponse,
};

/// The generated scanner proto
pub mod scanner {
tonic::include_proto!("scanner");
}

type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;

#[derive(Debug)]
/// The server implementation
pub struct ScannerRPC<ScanService>
where
ScanService: tower::Service<ScanServiceRequest, Response = ScanServiceResponse, Error = BoxError>
+ Clone
+ Send
+ Sync
+ 'static,
<ScanService as tower::Service<ScanServiceRequest>>::Future: Send,
{
scan_service: ScanService,
}

#[tonic::async_trait]
impl<ScanService> Scanner for ScannerRPC<ScanService>
where
ScanService: tower::Service<ScanServiceRequest, Response = ScanServiceResponse, Error = BoxError>
+ Clone
+ Send
+ Sync
+ 'static,
<ScanService as tower::Service<ScanServiceRequest>>::Future: Send,
{
async fn get_info(
&self,
_request: tonic::Request<Empty>,
) -> Result<Response<InfoReply>, Status> {
let ScanServiceResponse::Info {
min_sapling_birthday_height,
} = self
.scan_service
.clone()
.ready()
.and_then(|service| service.call(ScanServiceRequest::Info))
.await
.map_err(|_| Status::unknown("scan service was unavailable"))?
else {
return Err(Status::unknown(
"scan service returned an unexpected response",
));
};

let reply = scanner::InfoReply {
min_sapling_birthday_height: min_sapling_birthday_height.0,
};

Ok(Response::new(reply))
}
}

/// Initializes the zebra-scan gRPC server
pub async fn init<ScanService>(scan_service: ScanService) -> Result<(), color_eyre::Report>
where
ScanService: tower::Service<ScanServiceRequest, Response = ScanServiceResponse, Error = BoxError>
+ Clone
+ Send
+ Sync
+ 'static,
<ScanService as tower::Service<ScanServiceRequest>>::Future: Send,
{
let addr = "[::1]:50051".parse()?;
let service = ScannerRPC { scan_service };

Server::builder()
.add_service(ScannerServer::new(service))
.serve(addr)
.await?;

Ok(())
}
3 changes: 3 additions & 0 deletions zebra-node-services/src/scan_service/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
#[derive(Debug)]
/// Request types for `zebra_scan::service::ScanService`
pub enum Request {
/// Requests general info about the scanner
Info,

/// TODO: Accept `KeyHash`es and return key hashes that are registered
CheckKeyHashes(Vec<()>),

Expand Down
8 changes: 7 additions & 1 deletion zebra-node-services/src/scan_service/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@
use std::sync::{mpsc, Arc};

use zebra_chain::transaction::Transaction;
use zebra_chain::{block::Height, transaction::Transaction};

#[derive(Debug)]
/// Response types for `zebra_scan::service::ScanService`
pub enum Response {
/// Response to the `Info` request
Info {
/// The minimum sapling birthday height for the shielded scanner
min_sapling_birthday_height: Height,
},

/// Response to Results request
Results(Vec<Transaction>),

Expand Down
5 changes: 5 additions & 0 deletions zebra-scan/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ keywords = ["zebra", "zcash"]
# Must be one of <https://crates.io/category_slugs>
categories = ["cryptography::cryptocurrencies"]

[[bin]] # Bin to run the Scanner gRPC server
name = "scanner-grpc-server"
path = "src/bin/rpc_server.rs"

[features]

# Production features that activate extra dependencies, or extra features in dependencies
Expand Down Expand Up @@ -52,6 +56,7 @@ zcash_primitives = "0.13.0-rc.1"
zebra-chain = { path = "../zebra-chain", version = "1.0.0-beta.34" }
zebra-state = { path = "../zebra-state", version = "1.0.0-beta.34", features = ["shielded-scan"] }
zebra-node-services = { path = "../zebra-node-services", version = "1.0.0-beta.33" }
zebra-grpc = { path = "../zebra-grpc", version = "0.1.0-alpha.1" }

chrono = { version = "0.4.32", default-features = false, features = ["clock", "std", "serde"] }

Expand Down
19 changes: 19 additions & 0 deletions zebra-scan/src/bin/rpc_server.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//! Runs an RPC server with a mock ScanTask
use tower::ServiceBuilder;

use zebra_scan::service::ScanService;

#[tokio::main]
/// Runs an RPC server with a mock ScanTask
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let (config, network) = Default::default();
let scan_service = ServiceBuilder::new()
.buffer(10)
.service(ScanService::new_with_mock_scanner(&config, network));

// Start the gRPC server.
zebra_grpc::server::init(scan_service).await?;

Ok(())
}
40 changes: 26 additions & 14 deletions zebra-scan/src/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ use std::sync::{mpsc, Arc};

use color_eyre::Report;
use tokio::{sync::oneshot, task::JoinHandle};
use tracing::Instrument;
use tower::ServiceBuilder;

use zebra_chain::{diagnostic::task::WaitForPanics, parameters::Network, transaction::Transaction};
use zebra_chain::{parameters::Network, transaction::Transaction};
use zebra_state::ChainTipChange;

use crate::{scan, storage::Storage, Config};
use crate::{scan, service::ScanService, Config};

#[derive(Debug)]
/// Commands that can be sent to [`ScanTask`]
Expand Down Expand Up @@ -47,6 +47,16 @@ pub struct ScanTask {
}

impl ScanTask {
/// Spawns a new [`ScanTask`] for tests.
pub fn mock() -> Self {
let (cmd_sender, _cmd_receiver) = mpsc::channel();

Self {
handle: tokio::spawn(std::future::pending()),
cmd_sender,
}
}

/// Spawns a new [`ScanTask`].
pub fn spawn(
config: &Config,
Expand All @@ -58,7 +68,7 @@ impl ScanTask {
let (cmd_sender, _cmd_receiver) = mpsc::channel();

Self {
handle: spawn_init(config, network, state, chain_tip_change),
handle: scan::spawn_init(config, network, state, chain_tip_change),
cmd_sender,
}
}
Expand All @@ -81,13 +91,10 @@ pub fn spawn_init(
state: scan::State,
chain_tip_change: ChainTipChange,
) -> JoinHandle<Result<(), Report>> {
let config = config.clone();

// TODO: spawn an entirely new executor here, to avoid timing attacks.
tokio::spawn(init(config, network, state, chain_tip_change).in_current_span())
scan::spawn_init(config, network, state, chain_tip_change)
}

/// Initialize the scanner based on its config.
/// Initialize [`ScanService`] based on its config.
///
/// TODO: add a test for this function.
pub async fn init(
Expand All @@ -96,10 +103,15 @@ pub async fn init(
state: scan::State,
chain_tip_change: ChainTipChange,
) -> Result<(), Report> {
let storage = tokio::task::spawn_blocking(move || Storage::new(&config, network, false))
.wait_for_panics()
.await;
let scan_service = ServiceBuilder::new().buffer(10).service(ScanService::new(
&config,
network,
state,
chain_tip_change,
));

// Start the gRPC server.
zebra_grpc::server::init(scan_service).await?;

// TODO: add more tasks here?
scan::start(state, chain_tip_change, storage).await
Ok(())
}
2 changes: 1 addition & 1 deletion zebra-scan/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pub mod storage;

use zebra_node_services::scan_service::{request::Request, response::Response};

mod service;
pub mod service;
#[cfg(any(test, feature = "proptest-impl"))]
pub mod tests;

Expand Down
39 changes: 38 additions & 1 deletion zebra-scan/src/scan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ use std::{

use color_eyre::{eyre::eyre, Report};
use itertools::Itertools;
use tokio::task::JoinHandle;
use tower::{buffer::Buffer, util::BoxService, Service, ServiceExt};

use tracing::Instrument;
use zcash_client_backend::{
data_api::ScannedBlock,
encoding::decode_extended_full_viewing_key,
Expand All @@ -34,7 +36,10 @@ use zebra_chain::{
};
use zebra_state::{ChainTipChange, SaplingScannedResult, TransactionIndex};

use crate::storage::{SaplingScanningKey, Storage};
use crate::{
storage::{SaplingScanningKey, Storage},
Config,
};

/// The generic state type used by the scanner.
pub type State = Buffer<
Expand Down Expand Up @@ -430,3 +435,35 @@ async fn tip_height(mut state: State) -> Result<Height, Report> {
_ => unreachable!("unmatched response to a state::Tip request"),
}
}

/// Initialize the scanner based on its config, and spawn a task for it.
///
/// TODO: add a test for this function.
pub fn spawn_init(
config: &Config,
network: Network,
state: State,
chain_tip_change: ChainTipChange,
) -> JoinHandle<Result<(), Report>> {
let config = config.clone();

// TODO: spawn an entirely new executor here, to avoid timing attacks.
tokio::spawn(init(config, network, state, chain_tip_change).in_current_span())
}

/// Initialize the scanner based on its config.
///
/// TODO: add a test for this function.
pub async fn init(
config: Config,
network: Network,
state: State,
chain_tip_change: ChainTipChange,
) -> Result<(), Report> {
let storage = tokio::task::spawn_blocking(move || Storage::new(&config, network, false))
.wait_for_panics()
.await;

// TODO: add more tasks here?
start(state, chain_tip_change, storage).await
}
Loading

0 comments on commit 78d33f3

Please sign in to comment.