From 690b293c50c95115f006485623575c537760ec3f Mon Sep 17 00:00:00 2001 From: Arya Date: Mon, 5 Feb 2024 19:06:22 -0500 Subject: [PATCH 01/14] adds clear_results RPC method for zebra-scan --- zebra-grpc/proto/scanner.proto | 6 ++++++ zebra-grpc/src/server.rs | 29 ++++++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/zebra-grpc/proto/scanner.proto b/zebra-grpc/proto/scanner.proto index 8974a253028..93b3db69a99 100644 --- a/zebra-grpc/proto/scanner.proto +++ b/zebra-grpc/proto/scanner.proto @@ -7,10 +7,16 @@ message Empty {} service Scanner { // Get information about the scanner service. rpc GetInfo (Empty) returns (InfoReply); + + rpc ClearResults(ClearResultsRequest) returns (Empty); } // A response to a GetInfo call. message InfoReply { // The minimum sapling height allowed. uint32 min_sapling_birthday_height = 1; +} + +message ClearResultsRequest { + repeated string keys = 1; } \ No newline at end of file diff --git a/zebra-grpc/src/server.rs b/zebra-grpc/src/server.rs index 808ebba8ff5..1e20f8a467e 100644 --- a/zebra-grpc/src/server.rs +++ b/zebra-grpc/src/server.rs @@ -5,7 +5,7 @@ use tonic::{transport::Server, Response, Status}; use tower::ServiceExt; use scanner::scanner_server::{Scanner, ScannerServer}; -use scanner::{Empty, InfoReply}; +use scanner::{ClearResultsRequest, Empty, InfoReply}; use zebra_node_services::scan_service::{ request::Request as ScanServiceRequest, response::Response as ScanServiceResponse, @@ -67,6 +67,33 @@ where Ok(Response::new(reply)) } + + async fn clear_results( + &self, + request: tonic::Request, + ) -> Result, Status> { + let keys = request.into_inner().keys; + + if keys.is_empty() { + let msg = "must provide the keys for which to clear results"; + return Err(Status::invalid_argument(msg)); + } + + let ScanServiceResponse::ClearedResults = self + .scan_service + .clone() + .ready() + .and_then(|service| service.call(ScanServiceRequest::ClearResults(keys))) + .await + .map_err(|err| Status::unknown(format!("scan service returned error: {err}")))? + else { + return Err(Status::unknown( + "scan service returned an unexpected response", + )); + }; + + Ok(Response::new(Empty {})) + } } /// Initializes the zebra-scan gRPC server From 3f4dce44c6b14c0987758f05140e01afd4054094 Mon Sep 17 00:00:00 2001 From: Arya Date: Mon, 5 Feb 2024 19:12:09 -0500 Subject: [PATCH 02/14] adds delete_keys rpc method --- zebra-grpc/proto/scanner.proto | 6 ++++++ zebra-grpc/src/server.rs | 29 ++++++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/zebra-grpc/proto/scanner.proto b/zebra-grpc/proto/scanner.proto index 93b3db69a99..be8b594761f 100644 --- a/zebra-grpc/proto/scanner.proto +++ b/zebra-grpc/proto/scanner.proto @@ -9,6 +9,8 @@ service Scanner { rpc GetInfo (Empty) returns (InfoReply); rpc ClearResults(ClearResultsRequest) returns (Empty); + + rpc DeleteKeys(DeleteKeysRequest) returns (Empty); } // A response to a GetInfo call. @@ -19,4 +21,8 @@ message InfoReply { message ClearResultsRequest { repeated string keys = 1; +} + +message DeleteKeysRequest { + repeated string keys = 1; } \ No newline at end of file diff --git a/zebra-grpc/src/server.rs b/zebra-grpc/src/server.rs index 1e20f8a467e..45eddf11e1d 100644 --- a/zebra-grpc/src/server.rs +++ b/zebra-grpc/src/server.rs @@ -5,7 +5,7 @@ use tonic::{transport::Server, Response, Status}; use tower::ServiceExt; use scanner::scanner_server::{Scanner, ScannerServer}; -use scanner::{ClearResultsRequest, Empty, InfoReply}; +use scanner::{ClearResultsRequest, DeleteKeysRequest, Empty, InfoReply}; use zebra_node_services::scan_service::{ request::Request as ScanServiceRequest, response::Response as ScanServiceResponse, @@ -94,6 +94,33 @@ where Ok(Response::new(Empty {})) } + + async fn delete_keys( + &self, + request: tonic::Request, + ) -> Result, Status> { + let keys = request.into_inner().keys; + + if keys.is_empty() { + let msg = "must provide the keys for which to clear results"; + return Err(Status::invalid_argument(msg)); + } + + let ScanServiceResponse::DeletedKeys = self + .scan_service + .clone() + .ready() + .and_then(|service| service.call(ScanServiceRequest::DeleteKeys(keys))) + .await + .map_err(|err| Status::unknown(format!("scan service returned error: {err}")))? + else { + return Err(Status::unknown( + "scan service returned an unexpected response", + )); + }; + + Ok(Response::new(Empty {})) + } } /// Initializes the zebra-scan gRPC server From a3107e73925e0356c1bf15c17e602b4f7e0ec436 Mon Sep 17 00:00:00 2001 From: Arya Date: Mon, 5 Feb 2024 19:28:37 -0500 Subject: [PATCH 03/14] adds docs --- zebra-grpc/proto/scanner.proto | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/zebra-grpc/proto/scanner.proto b/zebra-grpc/proto/scanner.proto index be8b594761f..95bfa1c168c 100644 --- a/zebra-grpc/proto/scanner.proto +++ b/zebra-grpc/proto/scanner.proto @@ -8,8 +8,12 @@ service Scanner { // Get information about the scanner service. rpc GetInfo (Empty) returns (InfoReply); + // Clear results for a set of keys without removing the keys from the scanner. + // This request does not stop the scanner from scanning blocks for these keys, it + // only clears past results. rpc ClearResults(ClearResultsRequest) returns (Empty); + // Deletes a set of keys and their results from the scanner rpc DeleteKeys(DeleteKeysRequest) returns (Empty); } @@ -19,10 +23,14 @@ message InfoReply { uint32 min_sapling_birthday_height = 1; } +// A request for clearing past results from the scanner cache. message ClearResultsRequest { + // Keys for which to clear results. repeated string keys = 1; } +// A request to delete keys, delete their results, and stop scanning for their results. message DeleteKeysRequest { + // Keys to delete from scanner. repeated string keys = 1; } \ No newline at end of file From 31db4ad8fb3428feee46eea6069c36c051f89d85 Mon Sep 17 00:00:00 2001 From: Arya Date: Tue, 6 Feb 2024 12:29:11 -0500 Subject: [PATCH 04/14] Update zebra-grpc/proto/scanner.proto Co-authored-by: Alfredo Garcia --- zebra-grpc/proto/scanner.proto | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zebra-grpc/proto/scanner.proto b/zebra-grpc/proto/scanner.proto index 95bfa1c168c..72ff408829c 100644 --- a/zebra-grpc/proto/scanner.proto +++ b/zebra-grpc/proto/scanner.proto @@ -13,7 +13,8 @@ service Scanner { // only clears past results. rpc ClearResults(ClearResultsRequest) returns (Empty); - // Deletes a set of keys and their results from the scanner + // Deletes a set of keys and their results from the scanner. + // This request stop the scanner from scanning blocks for the these keys. rpc DeleteKeys(DeleteKeysRequest) returns (Empty); } From e78a4d751a3d32a924ecf31f4db2fac4a24fbb53 Mon Sep 17 00:00:00 2001 From: Arya Date: Tue, 6 Feb 2024 12:30:25 -0500 Subject: [PATCH 05/14] Apply suggestions from code review --- zebra-grpc/src/server.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zebra-grpc/src/server.rs b/zebra-grpc/src/server.rs index 45eddf11e1d..39f2d02ffb3 100644 --- a/zebra-grpc/src/server.rs +++ b/zebra-grpc/src/server.rs @@ -75,7 +75,7 @@ where let keys = request.into_inner().keys; if keys.is_empty() { - let msg = "must provide the keys for which to clear results"; + let msg = "must provide at least 1 key for which to clear results"; return Err(Status::invalid_argument(msg)); } @@ -102,7 +102,7 @@ where let keys = request.into_inner().keys; if keys.is_empty() { - let msg = "must provide the keys for which to clear results"; + let msg = "must provide at least 1 key to delete"; return Err(Status::invalid_argument(msg)); } From b8f1512fbf2c4e7cfc748fcd176d614190ed8daf Mon Sep 17 00:00:00 2001 From: Arya Date: Tue, 6 Feb 2024 14:47:40 -0500 Subject: [PATCH 06/14] start zebra-scan gRPC server from zebrad start command --- zebra-scan/src/config.rs | 2 ++ zebra-scan/src/init.rs | 17 +++++++++++++++++ zebra-scan/src/lib.rs | 2 +- zebrad/src/commands/start.rs | 29 ++++++++++------------------- 4 files changed, 30 insertions(+), 20 deletions(-) diff --git a/zebra-scan/src/config.rs b/zebra-scan/src/config.rs index aba8a75ad0b..1df52ab5e75 100644 --- a/zebra-scan/src/config.rs +++ b/zebra-scan/src/config.rs @@ -41,6 +41,8 @@ impl Default for Config { fn default() -> Self { Self { sapling_keys_to_scan: IndexMap::new(), + + // TODO: Add a const generic for specifying the default cache_dir path, like 'zebra' or 'zebra-scan'? db_config: DbConfig::default(), } } diff --git a/zebra-scan/src/init.rs b/zebra-scan/src/init.rs index 360014348cd..323c5067db2 100644 --- a/zebra-scan/src/init.rs +++ b/zebra-scan/src/init.rs @@ -1,8 +1,10 @@ //! Initializing the scanner and gRPC server. use color_eyre::Report; +use tokio::task::JoinHandle; use tower::ServiceBuilder; +use tracing::Instrument; use zebra_chain::parameters::Network; use zebra_state::ChainTipChange; @@ -17,6 +19,7 @@ pub async fn init( state: scan::State, chain_tip_change: ChainTipChange, ) -> Result<(), Report> { + info!(?config, "starting scan service"); let scan_service = ServiceBuilder::new().buffer(10).service(ScanService::new( &config, network, @@ -24,8 +27,22 @@ pub async fn init( chain_tip_change, )); + // TODO: move this to zebra-grpc init() function and include addr + info!("starting scan gRPC server"); + // Start the gRPC server. zebra_grpc::server::init(scan_service).await?; Ok(()) } + +/// Initialize the scanner and its gRPC server based on its config, and spawn a task for it. +pub fn spawn_init( + config: Config, + network: Network, + state: scan::State, + chain_tip_change: ChainTipChange, +) -> JoinHandle> { + // TODO: spawn an entirely new executor here, to avoid timing attacks. + tokio::spawn(init(config, network, state, chain_tip_change).in_current_span()) +} diff --git a/zebra-scan/src/lib.rs b/zebra-scan/src/lib.rs index 59594c8b0e0..04a9300ff5d 100644 --- a/zebra-scan/src/lib.rs +++ b/zebra-scan/src/lib.rs @@ -21,6 +21,6 @@ pub use service::scan_task::scan; pub mod tests; pub use config::Config; -pub use init::init; +pub use init::{init, spawn_init}; pub use zcash_primitives::{sapling::SaplingIvk, zip32::DiversifiableFullViewingKey}; diff --git a/zebrad/src/commands/start.rs b/zebrad/src/commands/start.rs index 78af9b152be..0e113e974bd 100644 --- a/zebrad/src/commands/start.rs +++ b/zebrad/src/commands/start.rs @@ -301,25 +301,16 @@ impl StartCmd { #[cfg(feature = "shielded-scan")] // Spawn never ending scan task only if we have keys to scan for. - let (scan_task_handle, _cmd_sender) = - if !config.shielded_scan.sapling_keys_to_scan.is_empty() { - // TODO: log the number of keys and update the scan_task_starts() test - info!("spawning shielded scanner with configured viewing keys"); - let scan_task = zebra_scan::service::scan_task::ScanTask::spawn( - &config.shielded_scan, - config.network.network, - state, - chain_tip_change, - ); - - ( - std::sync::Arc::into_inner(scan_task.handle) - .expect("should only have one reference here"), - Some(scan_task.cmd_sender), - ) - } else { - (tokio::spawn(std::future::pending().in_current_span()), None) - }; + let scan_task_handle = { + // TODO: log the number of keys and update the scan_task_starts() test + info!("spawning shielded scanner with configured viewing keys"); + zebra_scan::spawn_init( + config.shielded_scan.clone(), + config.network.network, + state, + chain_tip_change, + ) + }; #[cfg(not(feature = "shielded-scan"))] // Spawn a dummy scan task which doesn't do anything and never finishes. From 3e94d748a617a14db4cfc495e5d166fcce22bd71 Mon Sep 17 00:00:00 2001 From: Arya Date: Tue, 6 Feb 2024 15:23:28 -0500 Subject: [PATCH 07/14] adds a test that the scanner starts with zebrad --- Cargo.lock | 1 + zebra-grpc/src/lib.rs | 5 +++++ zebra-grpc/src/server.rs | 13 +++++------- zebrad/Cargo.toml | 1 + zebrad/tests/acceptance.rs | 40 +++++++++++++++++++++++++++++++++++ zebrad/tests/common/config.rs | 3 ++- 6 files changed, 54 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f6f429089fc..35190ca4709 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6064,6 +6064,7 @@ dependencies = [ "vergen", "zebra-chain", "zebra-consensus", + "zebra-grpc", "zebra-network", "zebra-node-services", "zebra-rpc", diff --git a/zebra-grpc/src/lib.rs b/zebra-grpc/src/lib.rs index dea37a1d0f7..89198aed922 100644 --- a/zebra-grpc/src/lib.rs +++ b/zebra-grpc/src/lib.rs @@ -5,3 +5,8 @@ #![doc(html_root_url = "https://docs.rs/zebra_grpc")] pub mod server; + +/// The generated scanner proto +pub mod scanner { + tonic::include_proto!("scanner"); +} diff --git a/zebra-grpc/src/server.rs b/zebra-grpc/src/server.rs index 39f2d02ffb3..fc0c5f73fb7 100644 --- a/zebra-grpc/src/server.rs +++ b/zebra-grpc/src/server.rs @@ -4,17 +4,14 @@ use futures_util::future::TryFutureExt; use tonic::{transport::Server, Response, Status}; use tower::ServiceExt; -use scanner::scanner_server::{Scanner, ScannerServer}; -use scanner::{ClearResultsRequest, DeleteKeysRequest, 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"); -} +use crate::scanner::{ + scanner_server::{Scanner, ScannerServer}, + ClearResultsRequest, DeleteKeysRequest, Empty, InfoReply, +}; type BoxError = Box; @@ -61,7 +58,7 @@ where )); }; - let reply = scanner::InfoReply { + let reply = InfoReply { min_sapling_birthday_height: min_sapling_birthday_height.0, }; diff --git a/zebrad/Cargo.toml b/zebrad/Cargo.toml index c05d4f81329..64c42d1e63b 100644 --- a/zebrad/Cargo.toml +++ b/zebrad/Cargo.toml @@ -289,6 +289,7 @@ zebra-scan = { path = "../zebra-scan", features = ["proptest-impl"] } zebra-node-services = { path = "../zebra-node-services", features = ["rpc-client"] } zebra-test = { path = "../zebra-test" } +zebra-grpc = { path = "../zebra-grpc" } # Used by the checkpoint generation tests via the zebra-checkpoints feature # (the binaries in this crate won't be built unless their features are enabled). diff --git a/zebrad/tests/acceptance.rs b/zebrad/tests/acceptance.rs index 91ea1c8ac71..7ac5e7e6919 100644 --- a/zebrad/tests/acceptance.rs +++ b/zebrad/tests/acceptance.rs @@ -2866,6 +2866,46 @@ fn scan_task_starts() -> Result<()> { Ok(()) } +/// Test that the scanner gRPC server starts when the node starts. +#[tokio::test] +#[cfg(feature = "shielded-scan")] +async fn scan_rpc_server_starts() -> Result<()> { + use zebra_grpc::scanner::{scanner_client::ScannerClient, Empty}; + + let _init_guard = zebra_test::init(); + + let test_type = TestType::LaunchWithEmptyState { + launches_lightwalletd: false, + }; + + // Start zebra with the config. + let mut zebrad = testdir()? + .with_exact_config(&default_test_config(Mainnet)?)? + .spawn_child(args!["start"])? + .with_timeout(test_type.zebrad_timeout()); + + // Wait for the gRPC server to start + tokio::time::sleep(TINY_CHECKPOINT_TIMEOUT).await; + + let mut client = ScannerClient::connect("http://[::1]:50051").await?; + + let request = tonic::Request::new(Empty {}); + + client.get_info(request).await?; + + // Kill the node. + zebrad.kill(false)?; + + // Check that scan task started and the first scanning is done. + let output = zebrad.wait_with_output()?; + + // Make sure the command was killed + output.assert_was_killed()?; + output.assert_failure()?; + + Ok(()) +} + /// Test that the scanner can continue scanning where it was left when zebrad restarts. /// /// Needs a cache state close to the tip. A possible way to run it locally is: diff --git a/zebrad/tests/common/config.rs b/zebrad/tests/common/config.rs index b75113e8c41..a67f1d484c6 100644 --- a/zebrad/tests/common/config.rs +++ b/zebrad/tests/common/config.rs @@ -82,7 +82,8 @@ pub fn default_test_config(net: Network) -> Result { #[cfg(feature = "shielded-scan")] { - let shielded_scan = zebra_scan::Config::ephemeral(); + let mut shielded_scan = zebra_scan::Config::ephemeral(); + shielded_scan.db_config_mut().cache_dir = "zebra-scan".into(); let config = ZebradConfig { network, From c2acc3aae67420f610d30849054ac92915a025b1 Mon Sep 17 00:00:00 2001 From: Arya Date: Tue, 6 Feb 2024 19:22:46 -0500 Subject: [PATCH 08/14] adds a `listen_addr` field to the shielded scan config --- zebra-grpc/src/server.rs | 10 +++++++--- zebra-scan/src/bin/rpc_server.rs | 2 +- zebra-scan/src/config.rs | 17 +++++++++++++++- zebra-scan/src/init.rs | 25 ++++++++++++++++++++---- zebra-scan/src/lib.rs | 2 +- zebra-scan/src/service/scan_task/scan.rs | 1 - 6 files changed, 46 insertions(+), 11 deletions(-) diff --git a/zebra-grpc/src/server.rs b/zebra-grpc/src/server.rs index fc0c5f73fb7..c1d3e0bbdfb 100644 --- a/zebra-grpc/src/server.rs +++ b/zebra-grpc/src/server.rs @@ -1,5 +1,7 @@ //! The gRPC server implementation +use std::net::SocketAddr; + use futures_util::future::TryFutureExt; use tonic::{transport::Server, Response, Status}; use tower::ServiceExt; @@ -121,7 +123,10 @@ where } /// Initializes the zebra-scan gRPC server -pub async fn init(scan_service: ScanService) -> Result<(), color_eyre::Report> +pub async fn init( + listen_addr: SocketAddr, + scan_service: ScanService, +) -> Result<(), color_eyre::Report> where ScanService: tower::Service + Clone @@ -130,12 +135,11 @@ where + 'static, >::Future: Send, { - let addr = "[::1]:50051".parse()?; let service = ScannerRPC { scan_service }; Server::builder() .add_service(ScannerServer::new(service)) - .serve(addr) + .serve(listen_addr) .await?; Ok(()) diff --git a/zebra-scan/src/bin/rpc_server.rs b/zebra-scan/src/bin/rpc_server.rs index 9ebe2b00246..1463df3f8f0 100644 --- a/zebra-scan/src/bin/rpc_server.rs +++ b/zebra-scan/src/bin/rpc_server.rs @@ -14,7 +14,7 @@ async fn main() -> Result<(), Box> { let scan_service = ServiceBuilder::new().buffer(10).service(scan_service); // Start the gRPC server. - zebra_grpc::server::init(scan_service).await?; + zebra_grpc::server::init("127.0.0.1:8231".parse()?, scan_service).await?; Ok(()) } diff --git a/zebra-scan/src/config.rs b/zebra-scan/src/config.rs index 1df52ab5e75..b32112aa2cb 100644 --- a/zebra-scan/src/config.rs +++ b/zebra-scan/src/config.rs @@ -1,6 +1,6 @@ //! Configuration for blockchain scanning tasks. -use std::fmt::Debug; +use std::{fmt::Debug, net::SocketAddr}; use indexmap::IndexMap; use serde::{Deserialize, Serialize}; @@ -20,6 +20,20 @@ pub struct Config { // TODO: allow keys without birthdays pub sapling_keys_to_scan: IndexMap, + /// IP address and port for the zebra-scan gRPC server. + /// + /// Note: The gRPC server is disabled by default. + /// To enable the gRPC server, set a listen address in the config: + /// ```toml + /// [shielded-scan] + /// listen_addr = '127.0.0.1:8231' + /// ``` + /// + /// The recommended ports for the RPC server are: + /// - Mainnet: 127.0.0.1:8231 + /// - Testnet: 127.0.0.1:18231 + pub listen_addr: Option, + /// The scanner results database config. // // TODO: Remove fields that are only used by the state, and create a common database config. @@ -41,6 +55,7 @@ impl Default for Config { fn default() -> Self { Self { sapling_keys_to_scan: IndexMap::new(), + listen_addr: None, // TODO: Add a const generic for specifying the default cache_dir path, like 'zebra' or 'zebra-scan'? db_config: DbConfig::default(), diff --git a/zebra-scan/src/init.rs b/zebra-scan/src/init.rs index 323c5067db2..b40bfe56c08 100644 --- a/zebra-scan/src/init.rs +++ b/zebra-scan/src/init.rs @@ -1,5 +1,7 @@ //! Initializing the scanner and gRPC server. +use std::net::SocketAddr; + use color_eyre::Report; use tokio::task::JoinHandle; use tower::ServiceBuilder; @@ -13,7 +15,8 @@ use crate::{scan, service::ScanService, Config}; /// Initialize [`ScanService`] based on its config. /// /// TODO: add a test for this function. -pub async fn init( +pub async fn init_with_server( + listen_addr: SocketAddr, config: Config, network: Network, state: scan::State, @@ -31,7 +34,7 @@ pub async fn init( info!("starting scan gRPC server"); // Start the gRPC server. - zebra_grpc::server::init(scan_service).await?; + zebra_grpc::server::init(listen_addr, scan_service).await?; Ok(()) } @@ -43,6 +46,20 @@ pub fn spawn_init( state: scan::State, chain_tip_change: ChainTipChange, ) -> JoinHandle> { - // TODO: spawn an entirely new executor here, to avoid timing attacks. - tokio::spawn(init(config, network, state, chain_tip_change).in_current_span()) + if let Some(listen_addr) = config.listen_addr { + // TODO: spawn an entirely new executor here, to avoid timing attacks. + tokio::spawn( + init_with_server(listen_addr, config, network, state, chain_tip_change) + .in_current_span(), + ) + } else { + // TODO: spawn an entirely new executor here, to avoid timing attacks. + tokio::spawn( + async move { + let (_cmd_sender, cmd_receiver) = std::sync::mpsc::channel(); + scan::init(config, network, state, chain_tip_change, cmd_receiver).await + } + .in_current_span(), + ) + } } diff --git a/zebra-scan/src/lib.rs b/zebra-scan/src/lib.rs index 04a9300ff5d..e1cf2d93a85 100644 --- a/zebra-scan/src/lib.rs +++ b/zebra-scan/src/lib.rs @@ -21,6 +21,6 @@ pub use service::scan_task::scan; pub mod tests; pub use config::Config; -pub use init::{init, spawn_init}; +pub use init::{init_with_server, spawn_init}; pub use zcash_primitives::{sapling::SaplingIvk, zip32::DiversifiableFullViewingKey}; diff --git a/zebra-scan/src/service/scan_task/scan.rs b/zebra-scan/src/service/scan_task/scan.rs index 9af1e3ef97d..11b15e10df3 100644 --- a/zebra-scan/src/service/scan_task/scan.rs +++ b/zebra-scan/src/service/scan_task/scan.rs @@ -497,7 +497,6 @@ pub fn spawn_init( ) -> JoinHandle> { let config = config.clone(); - // TODO: spawn an entirely new executor here, to avoid timing attacks. tokio::spawn(init(config, network, state, chain_tip_change, cmd_receiver).in_current_span()) } From df0832b7bc2016e552c21eda2db734943a7eeef0 Mon Sep 17 00:00:00 2001 From: Arya Date: Tue, 6 Feb 2024 19:27:05 -0500 Subject: [PATCH 09/14] updates test to use a random port and set the listen_addr config field --- zebrad/tests/acceptance.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/zebrad/tests/acceptance.rs b/zebrad/tests/acceptance.rs index 7ac5e7e6919..f99dbb0c7aa 100644 --- a/zebrad/tests/acceptance.rs +++ b/zebrad/tests/acceptance.rs @@ -2878,16 +2878,21 @@ async fn scan_rpc_server_starts() -> Result<()> { launches_lightwalletd: false, }; + let port = random_known_port(); + let listen_addr = format!("127.0.0.1:{port}"); + let mut config = default_test_config(Mainnet)?; + config.shielded_scan.listen_addr = listen_addr.parse().ok(); + // Start zebra with the config. let mut zebrad = testdir()? - .with_exact_config(&default_test_config(Mainnet)?)? + .with_exact_config(&config)? .spawn_child(args!["start"])? .with_timeout(test_type.zebrad_timeout()); // Wait for the gRPC server to start tokio::time::sleep(TINY_CHECKPOINT_TIMEOUT).await; - let mut client = ScannerClient::connect("http://[::1]:50051").await?; + let mut client = ScannerClient::connect(format!("http://{listen_addr}")).await?; let request = tonic::Request::new(Empty {}); From bfba7675353080c38020fe8308af09282f6bd63b Mon Sep 17 00:00:00 2001 From: Arya Date: Tue, 6 Feb 2024 19:50:07 -0500 Subject: [PATCH 10/14] fixes test --- zebrad/tests/acceptance.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/zebrad/tests/acceptance.rs b/zebrad/tests/acceptance.rs index f99dbb0c7aa..505f6c5498e 100644 --- a/zebrad/tests/acceptance.rs +++ b/zebrad/tests/acceptance.rs @@ -2881,7 +2881,7 @@ async fn scan_rpc_server_starts() -> Result<()> { let port = random_known_port(); let listen_addr = format!("127.0.0.1:{port}"); let mut config = default_test_config(Mainnet)?; - config.shielded_scan.listen_addr = listen_addr.parse().ok(); + config.shielded_scan.listen_addr = Some(listen_addr.parse()?); // Start zebra with the config. let mut zebrad = testdir()? @@ -2889,8 +2889,10 @@ async fn scan_rpc_server_starts() -> Result<()> { .spawn_child(args!["start"])? .with_timeout(test_type.zebrad_timeout()); - // Wait for the gRPC server to start - tokio::time::sleep(TINY_CHECKPOINT_TIMEOUT).await; + // Wait until gRPC server is starting. + tokio::time::sleep(LAUNCH_DELAY).await; + zebrad.expect_stdout_line_matches("starting scan gRPC server")?; + tokio::time::sleep(Duration::from_secs(1)).await; let mut client = ScannerClient::connect(format!("http://{listen_addr}")).await?; From 30a681b0d37394ce9df693e78ab4b80b81022053 Mon Sep 17 00:00:00 2001 From: Arya Date: Wed, 7 Feb 2024 14:03:54 -0500 Subject: [PATCH 11/14] Update zebra-scan/src/config.rs Co-authored-by: Alfredo Garcia --- zebra-scan/src/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zebra-scan/src/config.rs b/zebra-scan/src/config.rs index b32112aa2cb..915a5e32342 100644 --- a/zebra-scan/src/config.rs +++ b/zebra-scan/src/config.rs @@ -29,7 +29,7 @@ pub struct Config { /// listen_addr = '127.0.0.1:8231' /// ``` /// - /// The recommended ports for the RPC server are: + /// The recommended ports for the gRPC server are: /// - Mainnet: 127.0.0.1:8231 /// - Testnet: 127.0.0.1:18231 pub listen_addr: Option, From 3ca3af0ec5e491e9ef4f52e52b3d821b711faf1c Mon Sep 17 00:00:00 2001 From: Arya Date: Wed, 7 Feb 2024 14:36:50 -0500 Subject: [PATCH 12/14] fixes panic when trying to open multiple mutable storage instances. --- zebra-scan/src/init.rs | 5 +++-- zebra-scan/src/service.rs | 6 ++++-- zebra-scan/src/service/scan_task.rs | 18 +++------------- zebra-scan/src/service/scan_task/scan.rs | 26 ++---------------------- 4 files changed, 12 insertions(+), 43 deletions(-) diff --git a/zebra-scan/src/init.rs b/zebra-scan/src/init.rs index b40bfe56c08..294a0870c32 100644 --- a/zebra-scan/src/init.rs +++ b/zebra-scan/src/init.rs @@ -10,7 +10,7 @@ use tracing::Instrument; use zebra_chain::parameters::Network; use zebra_state::ChainTipChange; -use crate::{scan, service::ScanService, Config}; +use crate::{scan, service::ScanService, storage::Storage, Config}; /// Initialize [`ScanService`] based on its config. /// @@ -56,8 +56,9 @@ pub fn spawn_init( // TODO: spawn an entirely new executor here, to avoid timing attacks. tokio::spawn( async move { + let db = Storage::new(&config, network, false); let (_cmd_sender, cmd_receiver) = std::sync::mpsc::channel(); - scan::init(config, network, state, chain_tip_change, cmd_receiver).await + scan::start(state, chain_tip_change, db, cmd_receiver).await } .in_current_span(), ) diff --git a/zebra-scan/src/service.rs b/zebra-scan/src/service.rs index 3dee20f787d..0416a646367 100644 --- a/zebra-scan/src/service.rs +++ b/zebra-scan/src/service.rs @@ -42,9 +42,11 @@ impl ScanService { state: scan::State, chain_tip_change: ChainTipChange, ) -> Self { + let db = Storage::new(config, network, false); + Self { - db: Storage::new(config, network, false), - scan_task: ScanTask::spawn(config, network, state, chain_tip_change), + scan_task: ScanTask::spawn(db.clone(), state, chain_tip_change), + db, } } diff --git a/zebra-scan/src/service/scan_task.rs b/zebra-scan/src/service/scan_task.rs index f149b3834c0..d7bd4a04f6b 100644 --- a/zebra-scan/src/service/scan_task.rs +++ b/zebra-scan/src/service/scan_task.rs @@ -5,10 +5,9 @@ use std::sync::{mpsc, Arc}; use color_eyre::Report; use tokio::task::JoinHandle; -use zebra_chain::parameters::Network; use zebra_state::ChainTipChange; -use crate::Config; +use crate::storage::Storage; mod commands; mod executor; @@ -31,23 +30,12 @@ pub struct ScanTask { impl ScanTask { /// Spawns a new [`ScanTask`]. - pub fn spawn( - config: &Config, - network: Network, - state: scan::State, - chain_tip_change: ChainTipChange, - ) -> Self { + pub fn spawn(db: Storage, state: scan::State, chain_tip_change: ChainTipChange) -> Self { // TODO: Use a bounded channel or move this logic to the scan service or another service. let (cmd_sender, cmd_receiver) = mpsc::channel(); Self { - handle: Arc::new(scan::spawn_init( - config, - network, - state, - chain_tip_change, - cmd_receiver, - )), + handle: Arc::new(scan::spawn_init(db, state, chain_tip_change, cmd_receiver)), cmd_sender, } } diff --git a/zebra-scan/src/service/scan_task/scan.rs b/zebra-scan/src/service/scan_task/scan.rs index 11b15e10df3..e9afb36c30d 100644 --- a/zebra-scan/src/service/scan_task/scan.rs +++ b/zebra-scan/src/service/scan_task/scan.rs @@ -39,7 +39,6 @@ use zebra_state::{ChainTipChange, SaplingScannedResult, TransactionIndex}; use crate::{ service::{ScanTask, ScanTaskCommand}, storage::{SaplingScanningKey, Storage}, - Config, }; use super::executor; @@ -489,31 +488,10 @@ async fn tip_height(mut state: State) -> Result { /// /// TODO: add a test for this function. pub fn spawn_init( - config: &Config, - network: Network, + storage: Storage, state: State, chain_tip_change: ChainTipChange, cmd_receiver: Receiver, ) -> JoinHandle> { - let config = config.clone(); - - tokio::spawn(init(config, network, state, chain_tip_change, cmd_receiver).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, - cmd_receiver: Receiver, -) -> 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, cmd_receiver).await + tokio::spawn(start(state, chain_tip_change, storage, cmd_receiver).in_current_span()) } From aa62722f6ec365a252c5a1e3dee7a2ffc25087a3 Mon Sep 17 00:00:00 2001 From: Arya Date: Wed, 7 Feb 2024 14:43:41 -0500 Subject: [PATCH 13/14] open db in blocking task --- zebra-scan/src/init.rs | 18 +++++++++--------- zebra-scan/src/service.rs | 13 ++++++++----- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/zebra-scan/src/init.rs b/zebra-scan/src/init.rs index 294a0870c32..5344acc593a 100644 --- a/zebra-scan/src/init.rs +++ b/zebra-scan/src/init.rs @@ -7,7 +7,7 @@ use tokio::task::JoinHandle; use tower::ServiceBuilder; use tracing::Instrument; -use zebra_chain::parameters::Network; +use zebra_chain::{diagnostic::task::WaitForPanics, parameters::Network}; use zebra_state::ChainTipChange; use crate::{scan, service::ScanService, storage::Storage, Config}; @@ -23,12 +23,9 @@ pub async fn init_with_server( chain_tip_change: ChainTipChange, ) -> Result<(), Report> { info!(?config, "starting scan service"); - let scan_service = ServiceBuilder::new().buffer(10).service(ScanService::new( - &config, - network, - state, - chain_tip_change, - )); + let scan_service = ServiceBuilder::new() + .buffer(10) + .service(ScanService::new(&config, network, state, chain_tip_change).await); // TODO: move this to zebra-grpc init() function and include addr info!("starting scan gRPC server"); @@ -56,9 +53,12 @@ pub fn spawn_init( // TODO: spawn an entirely new executor here, to avoid timing attacks. tokio::spawn( async move { - let db = Storage::new(&config, network, false); + let storage = + tokio::task::spawn_blocking(move || Storage::new(&config, network, false)) + .wait_for_panics() + .await; let (_cmd_sender, cmd_receiver) = std::sync::mpsc::channel(); - scan::start(state, chain_tip_change, db, cmd_receiver).await + scan::start(state, chain_tip_change, storage, cmd_receiver).await } .in_current_span(), ) diff --git a/zebra-scan/src/service.rs b/zebra-scan/src/service.rs index 0416a646367..22d7bb16a95 100644 --- a/zebra-scan/src/service.rs +++ b/zebra-scan/src/service.rs @@ -5,7 +5,7 @@ use std::{collections::BTreeMap, future::Future, pin::Pin, task::Poll, time::Dur use futures::future::FutureExt; use tower::Service; -use zebra_chain::{parameters::Network, transaction::Hash}; +use zebra_chain::{diagnostic::task::WaitForPanics, parameters::Network, transaction::Hash}; use zebra_state::ChainTipChange; @@ -36,17 +36,20 @@ const DELETE_KEY_TIMEOUT: Duration = Duration::from_secs(15); impl ScanService { /// Create a new [`ScanService`]. - pub fn new( + pub async fn new( config: &Config, network: Network, state: scan::State, chain_tip_change: ChainTipChange, ) -> Self { - let db = Storage::new(config, network, false); + let config = config.clone(); + let storage = tokio::task::spawn_blocking(move || Storage::new(&config, network, false)) + .wait_for_panics() + .await; Self { - scan_task: ScanTask::spawn(db.clone(), state, chain_tip_change), - db, + scan_task: ScanTask::spawn(storage.clone(), state, chain_tip_change), + db: storage, } } From f03bb24ac1a90ea5015ba3de864625d80c7e700b Mon Sep 17 00:00:00 2001 From: Arya Date: Wed, 7 Feb 2024 14:46:17 -0500 Subject: [PATCH 14/14] fixes test --- zebrad/tests/common/shielded_scan/scans_for_new_key.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/zebrad/tests/common/shielded_scan/scans_for_new_key.rs b/zebrad/tests/common/shielded_scan/scans_for_new_key.rs index 35f0c320f07..20075b7a3ed 100644 --- a/zebrad/tests/common/shielded_scan/scans_for_new_key.rs +++ b/zebrad/tests/common/shielded_scan/scans_for_new_key.rs @@ -82,15 +82,13 @@ pub(crate) async fn run() -> Result<()> { tracing::info!("opened state service with valid chain tip height, deleting any past keys in db and starting scan task",); - { - // Before spawning `ScanTask`, delete past results for the zecpages key, if any. - let mut storage = Storage::new(&shielded_scan_config, network, false); - storage.delete_sapling_keys(vec![ZECPAGES_SAPLING_VIEWING_KEY.to_string()]); - } + // Before spawning `ScanTask`, delete past results for the zecpages key, if any. + let mut storage = Storage::new(&shielded_scan_config, network, false); + storage.delete_sapling_keys(vec![ZECPAGES_SAPLING_VIEWING_KEY.to_string()]); let state = ServiceBuilder::new().buffer(10).service(state_service); - let mut scan_task = ScanTask::spawn(&shielded_scan_config, network, state, chain_tip_change); + let mut scan_task = ScanTask::spawn(storage, state, chain_tip_change); let (zecpages_dfvks, zecpages_ivks) = sapling_key_to_scan_block_keys(&ZECPAGES_SAPLING_VIEWING_KEY.to_string(), network)?;