From fbddec3cb3de7bf6413ddbe47e21c0f33cdd0834 Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Fri, 16 Feb 2024 16:41:04 -0300 Subject: [PATCH] test(grpc): Add snapshots (#8277) * add grpc snapshot tests * replaces ScanService with MockService in snapshot tests * removes dev-dep in zebra-grpc on zebra-scan and updates snapshots * Apply suggestions from code review Co-authored-by: Alfredo Garcia --------- Co-authored-by: Arya --- Cargo.lock | 5 + zebra-grpc/Cargo.toml | 9 ++ zebra-grpc/build.rs | 1 + zebra-grpc/src/lib.rs | 3 + zebra-grpc/src/tests.rs | 1 + zebra-grpc/src/tests/snapshot.rs | 151 ++++++++++++++++++ .../src/tests/snapshots/get_info@mainnet.snap | 7 + .../src/tests/snapshots/get_info@testnet.snap | 7 + .../tests/snapshots/get_results@mainnet.snap | 33 ++++ .../tests/snapshots/get_results@testnet.snap | 33 ++++ zebra-scan/src/storage/db/sapling.rs | 2 +- 11 files changed, 251 insertions(+), 1 deletion(-) create mode 100644 zebra-grpc/src/tests.rs create mode 100644 zebra-grpc/src/tests/snapshot.rs create mode 100644 zebra-grpc/src/tests/snapshots/get_info@mainnet.snap create mode 100644 zebra-grpc/src/tests/snapshots/get_info@testnet.snap create mode 100644 zebra-grpc/src/tests/snapshots/get_results@mainnet.snap create mode 100644 zebra-grpc/src/tests/snapshots/get_results@testnet.snap diff --git a/Cargo.lock b/Cargo.lock index 3c164ab68..ec9909048 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5815,13 +5815,18 @@ version = "0.1.0-alpha.1" dependencies = [ "color-eyre", "futures-util", + "insta", "prost", + "serde", "tokio", "tonic 0.11.0", "tonic-build 0.11.0", "tower", "zcash_primitives", + "zebra-chain", "zebra-node-services", + "zebra-state", + "zebra-test", ] [[package]] diff --git a/zebra-grpc/Cargo.toml b/zebra-grpc/Cargo.toml index e7fb863bf..7de5a505d 100644 --- a/zebra-grpc/Cargo.toml +++ b/zebra-grpc/Cargo.toml @@ -19,6 +19,7 @@ categories = ["cryptography::cryptocurrencies"] futures-util = "0.3.28" tonic = "0.11.0" prost = "0.12.3" +serde = { version = "1.0.196", features = ["serde_derive"] } tokio = { version = "1.36.0", features = ["macros", "rt-multi-thread"] } tower = { version = "0.4.13", features = ["util", "buffer"] } color-eyre = "0.6.2" @@ -29,3 +30,11 @@ zebra-node-services = { path = "../zebra-node-services", version = "1.0.0-beta.3 [build-dependencies] tonic-build = "0.11.0" + +[dev-dependencies] +insta = { version = "1.33.0", features = ["redactions", "json", "ron"] } + +zebra-chain = { path = "../zebra-chain", features = ["proptest-impl"] } +zebra-state = { path = "../zebra-state" } +zebra-test = { path = "../zebra-test" } + diff --git a/zebra-grpc/build.rs b/zebra-grpc/build.rs index 0deb3a637..61423f87b 100644 --- a/zebra-grpc/build.rs +++ b/zebra-grpc/build.rs @@ -3,6 +3,7 @@ fn main() -> Result<(), Box> { tonic_build::configure() .btree_map(["."]) + .type_attribute(".", "#[derive(serde::Deserialize, serde::Serialize)]") .compile(&["proto/scanner.proto"], &[""])?; Ok(()) } diff --git a/zebra-grpc/src/lib.rs b/zebra-grpc/src/lib.rs index 89198aed9..44faf188d 100644 --- a/zebra-grpc/src/lib.rs +++ b/zebra-grpc/src/lib.rs @@ -6,6 +6,9 @@ pub mod server; +#[cfg(test)] +mod tests; + /// The generated scanner proto pub mod scanner { tonic::include_proto!("scanner"); diff --git a/zebra-grpc/src/tests.rs b/zebra-grpc/src/tests.rs new file mode 100644 index 000000000..86dad5269 --- /dev/null +++ b/zebra-grpc/src/tests.rs @@ -0,0 +1 @@ +mod snapshot; diff --git a/zebra-grpc/src/tests/snapshot.rs b/zebra-grpc/src/tests/snapshot.rs new file mode 100644 index 000000000..9d392c573 --- /dev/null +++ b/zebra-grpc/src/tests/snapshot.rs @@ -0,0 +1,151 @@ +//! Snapshot tests for Zebra Scan gRPC responses. +//! +//! Currently we snapshot the `get_info` and `get_results` responses for both mainnet and testnet with a +//! mocked scanner database. Calls that return `Empty` responses are not snapshoted in this suite. +//! +//! To update these snapshots, run: +//! ```sh +//! cargo insta test --review --delete-unreferenced-snapshots +//! ``` +use std::{collections::BTreeMap, thread::sleep, time::Duration}; + +use zebra_chain::{block::Height, parameters::Network, transaction}; +use zebra_test::mock_service::MockService; + +use zebra_node_services::scan_service::{ + request::Request as ScanRequest, response::Response as ScanResponse, +}; + +use crate::{ + scanner::{ + scanner_client::ScannerClient, Empty, GetResultsRequest, GetResultsResponse, InfoReply, + }, + server::init, +}; + +/// The extended Sapling viewing key of [ZECpages](https://zecpages.com/boardinfo) +pub const ZECPAGES_SAPLING_VIEWING_KEY: &str = "zxviews1q0duytgcqqqqpqre26wkl45gvwwwd706xw608hucmvfalr759ejwf7qshjf5r9aa7323zulvz6plhttp5mltqcgs9t039cx2d09mgq05ts63n8u35hyv6h9nc9ctqqtue2u7cer2mqegunuulq2luhq3ywjcz35yyljewa4mgkgjzyfwh6fr6jd0dzd44ghk0nxdv2hnv4j5nxfwv24rwdmgllhe0p8568sgqt9ckt02v2kxf5ahtql6s0ltjpkckw8gtymxtxuu9gcr0swvz"; + +#[tokio::test(flavor = "multi_thread")] +async fn test_grpc_response_data() { + let _init_guard = zebra_test::init(); + + tokio::join!( + test_mocked_rpc_response_data_for_network( + Network::Mainnet, + zebra_test::net::random_known_port() + ), + test_mocked_rpc_response_data_for_network( + Network::Testnet, + zebra_test::net::random_known_port() + ), + ); +} + +async fn test_mocked_rpc_response_data_for_network(network: Network, random_port: u16) { + // get a mocked scan service + let mock_scan_service = MockService::build().for_unit_tests(); + + // start the gRPC server + let listen_addr: std::net::SocketAddr = format!("127.0.0.1:{random_port}") + .parse() + .expect("hard-coded IP and u16 port should parse successfully"); + + { + let mock_scan_service = mock_scan_service.clone(); + tokio::spawn(async move { + init(listen_addr, mock_scan_service) + .await + .expect("Possible port conflict"); + }); + } + + // wait for the server to start + sleep(Duration::from_secs(1)); + + // connect to the gRPC server + let client = ScannerClient::connect(format!("http://127.0.0.1:{random_port}")) + .await + .expect("server should receive connection"); + + // insta settings + let mut settings = insta::Settings::clone_current(); + settings.set_snapshot_suffix(network.lowercase_name()); + + // snapshot the get_info grpc call + let get_info_response_fut = { + let mut client = client.clone(); + let get_info_request = tonic::Request::new(Empty {}); + tokio::spawn(async move { client.get_info(get_info_request).await }) + }; + + { + let mut mock_scan_service = mock_scan_service.clone(); + tokio::spawn(async move { + mock_scan_service + .expect_request_that(|req| matches!(req, ScanRequest::Info)) + .await + .respond(ScanResponse::Info { + min_sapling_birthday_height: network.sapling_activation_height(), + }) + }); + } + + // snapshot the get_info grpc call + + let get_info_response = get_info_response_fut + .await + .expect("tokio task should join successfully") + .expect("get_info request should succeed"); + + snapshot_rpc_getinfo(get_info_response.into_inner(), &settings); + + // snapshot the get_results grpc call + + let get_results_response_fut = { + let mut client = client.clone(); + let get_results_request = tonic::Request::new(GetResultsRequest { + keys: vec![ZECPAGES_SAPLING_VIEWING_KEY.to_string()], + }); + tokio::spawn(async move { client.get_results(get_results_request).await }) + }; + + { + let mut mock_scan_service = mock_scan_service.clone(); + tokio::spawn(async move { + let zec_pages_sapling_efvk = ZECPAGES_SAPLING_VIEWING_KEY.to_string(); + let mut fake_results = BTreeMap::new(); + for fake_result_height in [Height::MIN, Height(1), Height::MAX] { + fake_results.insert( + fake_result_height, + [transaction::Hash::from([0; 32])].repeat(3), + ); + } + + let mut fake_results_response = BTreeMap::new(); + fake_results_response.insert(zec_pages_sapling_efvk, fake_results); + + mock_scan_service + .expect_request_that(|req| matches!(req, ScanRequest::Results(_))) + .await + .respond(ScanResponse::Results(fake_results_response)) + }); + } + + let get_results_response = get_results_response_fut + .await + .expect("tokio task should join successfully") + .expect("get_results request should succeed"); + + snapshot_rpc_getresults(get_results_response.into_inner(), &settings); +} + +/// Snapshot `getinfo` response, using `cargo insta` and JSON serialization. +fn snapshot_rpc_getinfo(info: InfoReply, settings: &insta::Settings) { + settings.bind(|| insta::assert_json_snapshot!("get_info", info)); +} + +/// Snapshot `getresults` response, using `cargo insta` and JSON serialization. +fn snapshot_rpc_getresults(results: GetResultsResponse, settings: &insta::Settings) { + settings.bind(|| insta::assert_json_snapshot!("get_results", results)); +} diff --git a/zebra-grpc/src/tests/snapshots/get_info@mainnet.snap b/zebra-grpc/src/tests/snapshots/get_info@mainnet.snap new file mode 100644 index 000000000..ad15b2a1a --- /dev/null +++ b/zebra-grpc/src/tests/snapshots/get_info@mainnet.snap @@ -0,0 +1,7 @@ +--- +source: zebra-grpc/src/tests/snapshot.rs +expression: info +--- +{ + "min_sapling_birthday_height": 419200 +} diff --git a/zebra-grpc/src/tests/snapshots/get_info@testnet.snap b/zebra-grpc/src/tests/snapshots/get_info@testnet.snap new file mode 100644 index 000000000..9c39e93e8 --- /dev/null +++ b/zebra-grpc/src/tests/snapshots/get_info@testnet.snap @@ -0,0 +1,7 @@ +--- +source: zebra-grpc/src/tests/snapshot.rs +expression: info +--- +{ + "min_sapling_birthday_height": 280000 +} diff --git a/zebra-grpc/src/tests/snapshots/get_results@mainnet.snap b/zebra-grpc/src/tests/snapshots/get_results@mainnet.snap new file mode 100644 index 000000000..afba0d25d --- /dev/null +++ b/zebra-grpc/src/tests/snapshots/get_results@mainnet.snap @@ -0,0 +1,33 @@ +--- +source: zebra-grpc/src/tests/snapshot.rs +expression: results +--- +{ + "results": { + "zxviews1q0duytgcqqqqpqre26wkl45gvwwwd706xw608hucmvfalr759ejwf7qshjf5r9aa7323zulvz6plhttp5mltqcgs9t039cx2d09mgq05ts63n8u35hyv6h9nc9ctqqtue2u7cer2mqegunuulq2luhq3ywjcz35yyljewa4mgkgjzyfwh6fr6jd0dzd44ghk0nxdv2hnv4j5nxfwv24rwdmgllhe0p8568sgqt9ckt02v2kxf5ahtql6s0ltjpkckw8gtymxtxuu9gcr0swvz": { + "transactions": { + "0": { + "hash": [ + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000" + ] + }, + "1": { + "hash": [ + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000" + ] + }, + "2147483647": { + "hash": [ + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000" + ] + } + } + } + } +} diff --git a/zebra-grpc/src/tests/snapshots/get_results@testnet.snap b/zebra-grpc/src/tests/snapshots/get_results@testnet.snap new file mode 100644 index 000000000..afba0d25d --- /dev/null +++ b/zebra-grpc/src/tests/snapshots/get_results@testnet.snap @@ -0,0 +1,33 @@ +--- +source: zebra-grpc/src/tests/snapshot.rs +expression: results +--- +{ + "results": { + "zxviews1q0duytgcqqqqpqre26wkl45gvwwwd706xw608hucmvfalr759ejwf7qshjf5r9aa7323zulvz6plhttp5mltqcgs9t039cx2d09mgq05ts63n8u35hyv6h9nc9ctqqtue2u7cer2mqegunuulq2luhq3ywjcz35yyljewa4mgkgjzyfwh6fr6jd0dzd44ghk0nxdv2hnv4j5nxfwv24rwdmgllhe0p8568sgqt9ckt02v2kxf5ahtql6s0ltjpkckw8gtymxtxuu9gcr0swvz": { + "transactions": { + "0": { + "hash": [ + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000" + ] + }, + "1": { + "hash": [ + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000" + ] + }, + "2147483647": { + "hash": [ + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000" + ] + } + } + } + } +} diff --git a/zebra-scan/src/storage/db/sapling.rs b/zebra-scan/src/storage/db/sapling.rs index 6892fc30a..b3c7b870b 100644 --- a/zebra-scan/src/storage/db/sapling.rs +++ b/zebra-scan/src/storage/db/sapling.rs @@ -159,7 +159,7 @@ impl Storage { /// Inserts a batch of scanned sapling result for a key and height. /// If a result already exists for that key, height, and index, it is replaced. - pub(crate) fn insert_sapling_results( + pub fn insert_sapling_results( &mut self, sapling_key: &SaplingScanningKey, height: Height,