Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(grpc): Add initial Getinfo grpc #8178

Merged
merged 14 commits into from
Jan 26, 2024
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
Loading