From cdb9efdb276546a2c719e74b25570792b21ab7f2 Mon Sep 17 00:00:00 2001 From: Gustavo Valverde Date: Thu, 29 Aug 2024 20:57:17 +0100 Subject: [PATCH 01/15] fix(docker): allow the `zebra` user access to relevant dirs (#8817) * fix(docker): allow the `zebra` user access to relevant dirs When runnning a Zebra node using Docker without a privileged user, you won't be able to modify some files and directories, not even the ones in the current directory, as the `zebra` user has no permission to `/`. The best way to solve this is making the `/opt/zebrad` the current `WORKDIR`. This also requires moving the `entrypoint.sh` from the root `/` directory to `/etc/zebrad` as this directory is used to save configuration, and other files. An `APP_HOME` ARG is used as not all platforms where a Docker container is deployed allows writting permissions to the `/opt` directory. This allow some users to re-build the image with a custom `WORKDIR` * fix(docker): allow starting the container without a `zebrad` command As `gosu` is just required and available in our `runtime` image, trying to run `docker run -it --rm --name tests -t zfnd/zebra: /bin/bash` in other stages will fail, as `gosu` is not available. --- docker/Dockerfile | 40 +++++++++++++++++++++++++--------------- docker/entrypoint.sh | 10 +++++++--- 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 3820d87adf4..1ccaa5915a5 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -19,10 +19,14 @@ ARG FEATURES="default-release-binaries" ARG TEST_FEATURES="lightwalletd-grpc-tests zebra-checkpoints" ARG EXPERIMENTAL_FEATURES="" +ARG APP_HOME="/opt/zebrad" # This stage implements cargo-chef for docker layer caching FROM rust:bookworm as chef RUN cargo install cargo-chef --locked -WORKDIR /opt/zebrad + +ARG APP_HOME +ENV APP_HOME=${APP_HOME} +WORKDIR ${APP_HOME} # Analyze the current project to determine the minimum subset of files # (Cargo.lock and Cargo.toml manifests) required to build it and cache dependencies @@ -38,7 +42,7 @@ RUN cargo chef prepare --recipe-path recipe.json # We set defaults for the arguments, in case the build does not include this information. FROM chef AS deps SHELL ["/bin/bash", "-xo", "pipefail", "-c"] -COPY --from=planner /opt/zebrad/recipe.json recipe.json +COPY --from=planner ${APP_HOME}/recipe.json recipe.json # Install zebra build deps and Dockerfile deps RUN apt-get -qq update && \ @@ -90,7 +94,7 @@ ARG SHORT_SHA # https://github.com/ZcashFoundation/zebra/blob/9ebd56092bcdfc1a09062e15a0574c94af37f389/zebrad/src/application.rs#L179-L182 ENV SHORT_SHA=${SHORT_SHA:-} -ENV CARGO_HOME="/opt/zebrad/.cargo/" +ENV CARGO_HOME="${APP_HOME}/.cargo/" # In this stage we build tests (without running then) # @@ -128,17 +132,16 @@ RUN cargo chef cook --tests --release --features "${ENTRYPOINT_FEATURES}" --work # Undo the source file changes made by cargo-chef. # rsync invalidates the cargo cache for the changed files only, by updating their timestamps. # This makes sure the fake empty binaries created by cargo-chef are rebuilt. -COPY --from=planner /opt/zebrad zebra-original +COPY --from=planner ${APP_HOME} zebra-original RUN rsync --recursive --checksum --itemize-changes --verbose zebra-original/ . RUN rm -r zebra-original # Build Zebra test binaries, but don't run them RUN cargo test --locked --release --features "${ENTRYPOINT_FEATURES}" --workspace --no-run -RUN cp /opt/zebrad/target/release/zebrad /usr/local/bin -RUN cp /opt/zebrad/target/release/zebra-checkpoints /usr/local/bin +RUN cp ${APP_HOME}/target/release/zebrad /usr/local/bin +RUN cp ${APP_HOME}/target/release/zebra-checkpoints /usr/local/bin -COPY ./docker/entrypoint.sh / -RUN chmod u+x /entrypoint.sh +COPY ./docker/entrypoint.sh /etc/zebrad/entrypoint.sh # Entrypoint environment variables ENV ENTRYPOINT_FEATURES=${ENTRYPOINT_FEATURES} @@ -147,7 +150,7 @@ ARG EXPERIMENTAL_FEATURES="shielded-scan journald prometheus filter-reload" ENV ENTRYPOINT_FEATURES_EXPERIMENTAL="${ENTRYPOINT_FEATURES} ${EXPERIMENTAL_FEATURES}" # By default, runs the entrypoint tests specified by the environmental variables (if any are set) -ENTRYPOINT [ "/entrypoint.sh" ] +ENTRYPOINT [ "/etc/zebrad/entrypoint.sh" ] # In this stage we build a release (generate the zebrad binary) # @@ -167,15 +170,14 @@ ARG FEATURES RUN cargo chef cook --release --features "${FEATURES}" --package zebrad --bin zebrad --recipe-path recipe.json # Undo the source file changes made by cargo-chef, so the fake empty zebrad binary is rebuilt. -COPY --from=planner /opt/zebrad zebra-original +COPY --from=planner ${APP_HOME} zebra-original RUN rsync --recursive --checksum --itemize-changes --verbose zebra-original/ . RUN rm -r zebra-original # Build zebrad RUN cargo build --locked --release --features "${FEATURES}" --package zebrad --bin zebrad -COPY ./docker/entrypoint.sh / -RUN chmod u+x /entrypoint.sh +COPY ./docker/entrypoint.sh ./ # This stage is only used when deploying nodes or when only the resulting zebrad binary is needed # @@ -183,6 +185,11 @@ RUN chmod u+x /entrypoint.sh # binary from the `release` stage FROM debian:bookworm-slim AS runtime +# Set the default path for the zebrad binary +ARG APP_HOME +ENV APP_HOME=${APP_HOME} +WORKDIR ${APP_HOME} + RUN apt-get update && \ apt-get install -y --no-install-recommends \ ca-certificates \ @@ -220,13 +227,16 @@ ENV FEATURES=${FEATURES} ENV ZEBRA_CONF_DIR=${ZEBRA_CONF_DIR:-/etc/zebrad} ENV ZEBRA_CONF_FILE=${ZEBRA_CONF_FILE:-zebrad.toml} -COPY --from=release /opt/zebrad/target/release/zebrad /usr/local/bin -COPY --from=release /entrypoint.sh / +RUN mkdir -p ${ZEBRA_CONF_DIR} && chown ${UID}:${UID} ${ZEBRA_CONF_DIR} \ + && chown ${UID}:${UID} ${APP_HOME} + +COPY --from=release ${APP_HOME}/target/release/zebrad /usr/local/bin +COPY --from=release ${APP_HOME}/entrypoint.sh /etc/zebrad # Expose configured ports EXPOSE 8233 18233 # Update the config file based on the Docker run variables, # and launch zebrad with it -ENTRYPOINT [ "/entrypoint.sh" ] +ENTRYPOINT [ "/etc/zebrad/entrypoint.sh" ] CMD ["zebrad"] diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 3dd5275d643..b67cf5ee5b5 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -357,11 +357,15 @@ case "$1" in exec cargo test --locked --release --features "zebra-test" --package zebra-scan -- --nocapture --include-ignored scan_task_commands else - exec gosu "$USER" "$@" + exec "$@" fi fi ;; *) - exec gosu "$USER" "$@" + if command -v gosu >/dev/null 2>&1; then + exec gosu "$USER" "$@" + else + exec "$@" + fi ;; -esac +esac \ No newline at end of file From 0ef9987e9eeaae4d9af121264f66a866501d4cee Mon Sep 17 00:00:00 2001 From: Arya Date: Thu, 29 Aug 2024 17:09:27 -0400 Subject: [PATCH 02/15] fix(state): Write database format version to disk atomically to avoid a rare panic (#8795) * Splits `atomic_write_to_tmp_file` out of `zebra_network::Config::update_peer_cache` * Uses the new `atomic_write_to_tmp_file` fn in `update_peer_cache()` * Replaces repetitive code for getting the default peer and state cache directories with `default_cache_dir()` * Converts `atomic_write_to_tmp_file` to a blocking function and adds `spawn_atomic_write_to_tmp_file` for use in async environments. * Uses `atomic_write_to_tmp_file` to write database versions to disk * Removes `spawn_atomic_write_to_tmp_file()` and inlines its body at its callsite to avoid adding tokio as a dependency of zebra-chain. * Apply suggestions from code review Co-authored-by: Marek --------- Co-authored-by: Marek --- Cargo.lock | 2 + zebra-chain/Cargo.toml | 2 + zebra-chain/src/common.rs | 71 ++++++++++++++++ zebra-chain/src/lib.rs | 1 + zebra-network/src/config.rs | 111 +++++++------------------- zebra-network/src/config/cache_dir.rs | 9 +-- zebra-state/src/config.rs | 17 ++-- 7 files changed, 113 insertions(+), 100 deletions(-) create mode 100644 zebra-chain/src/common.rs diff --git a/Cargo.lock b/Cargo.lock index 76374083800..c5169cf98dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5964,6 +5964,7 @@ dependencies = [ "chrono", "color-eyre", "criterion", + "dirs", "ed25519-zebra", "equihash 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "futures", @@ -5996,6 +5997,7 @@ dependencies = [ "sha2", "spandoc", "static_assertions", + "tempfile", "thiserror", "tinyvec", "tokio", diff --git a/zebra-chain/Cargo.toml b/zebra-chain/Cargo.toml index a7a4a7efc05..a6ec5c1f342 100644 --- a/zebra-chain/Cargo.toml +++ b/zebra-chain/Cargo.toml @@ -81,6 +81,8 @@ group = "0.13.0" incrementalmerkletree.workspace = true jubjub = "0.10.0" lazy_static = "1.4.0" +tempfile = "3.11.0" +dirs = "5.0.1" num-integer = "0.1.46" primitive-types = "0.12.2" rand_core = "0.6.4" diff --git a/zebra-chain/src/common.rs b/zebra-chain/src/common.rs new file mode 100644 index 00000000000..2af22887d8d --- /dev/null +++ b/zebra-chain/src/common.rs @@ -0,0 +1,71 @@ +//! Common functions used in Zebra. + +use std::{ + ffi::OsString, + fs, + io::{self, Write}, + path::PathBuf, +}; + +use tempfile::PersistError; + +/// Returns Zebra's default cache directory path. +pub fn default_cache_dir() -> PathBuf { + dirs::cache_dir() + .unwrap_or_else(|| std::env::current_dir().unwrap().join("cache")) + .join("zebra") +} + +/// Accepts a target file path and a byte-slice. +/// +/// Atomically writes the byte-slice to a file to avoid corrupting the file if Zebra +/// panics, crashes, or exits while the file is being written, or if multiple Zebra instances +/// try to read and write the same file. +/// +/// Returns the provided file path if successful. +/// +/// # Concurrency +/// +/// This function blocks on filesystem operations and should be called in a blocking task +/// when calling from an async environment. +/// +/// # Panics +/// +/// If the provided `file_path` is a directory path. +pub fn atomic_write( + file_path: PathBuf, + data: &[u8], +) -> io::Result>> { + // Get the file's parent directory, or use Zebra's default cache directory + let file_dir = file_path + .parent() + .map(|p| p.to_owned()) + .unwrap_or_else(default_cache_dir); + + // Create the directory if needed. + fs::create_dir_all(&file_dir)?; + + // Give the temporary file a similar name to the permanent file, + // but hide it in directory listings. + let mut tmp_file_prefix: OsString = ".tmp.".into(); + tmp_file_prefix.push( + file_path + .file_name() + .expect("file path must have a file name"), + ); + + // Create the temporary file in the same directory as the permanent file, + // so atomic filesystem operations are possible. + let mut tmp_file = tempfile::Builder::new() + .prefix(&tmp_file_prefix) + .tempfile_in(file_dir)?; + + tmp_file.write_all(data)?; + + // Atomically write the temp file to `file_path`. + let persist_result = tmp_file + .persist(&file_path) + // Drops the temp file and returns the file path. + .map(|_| file_path); + Ok(persist_result) +} diff --git a/zebra-chain/src/lib.rs b/zebra-chain/src/lib.rs index 4faaeab70cc..460d3a850f0 100644 --- a/zebra-chain/src/lib.rs +++ b/zebra-chain/src/lib.rs @@ -22,6 +22,7 @@ pub mod amount; pub mod block; pub mod chain_sync_status; pub mod chain_tip; +pub mod common; pub mod diagnostic; pub mod error; pub mod fmt; diff --git a/zebra-network/src/config.rs b/zebra-network/src/config.rs index 00f4f8b4460..7936ea0e787 100644 --- a/zebra-network/src/config.rs +++ b/zebra-network/src/config.rs @@ -2,7 +2,6 @@ use std::{ collections::HashSet, - ffi::OsString, io::{self, ErrorKind}, net::{IpAddr, SocketAddr}, time::Duration, @@ -10,11 +9,11 @@ use std::{ use indexmap::IndexSet; use serde::{de, Deserialize, Deserializer}; -use tempfile::NamedTempFile; -use tokio::{fs, io::AsyncWriteExt}; -use tracing::Span; +use tokio::fs; +use tracing::Span; use zebra_chain::{ + common::atomic_write, parameters::{ testnet::{self, ConfiguredActivationHeights, ConfiguredFundingStreams}, Magic, Network, NetworkKind, @@ -503,90 +502,36 @@ impl Config { // Make a newline-separated list let peer_data = peer_list.join("\n"); - // Write to a temporary file, so the cache is not corrupted if Zebra shuts down or crashes - // at the same time. - // - // # Concurrency - // - // We want to use async code to avoid blocking the tokio executor on filesystem operations, - // but `tempfile` is implemented using non-asyc methods. So we wrap its filesystem - // operations in `tokio::spawn_blocking()`. - // - // TODO: split this out into an atomic_write_to_tmp_file() method if we need to re-use it - - // Create the peer cache directory if needed - let peer_cache_dir = peer_cache_file - .parent() - .expect("cache path always has a network directory") - .to_owned(); - tokio::fs::create_dir_all(&peer_cache_dir).await?; - - // Give the temporary file a similar name to the permanent cache file, - // but hide it in directory listings. - let mut tmp_peer_cache_prefix: OsString = ".tmp.".into(); - tmp_peer_cache_prefix.push( - peer_cache_file - .file_name() - .expect("cache file always has a file name"), - ); - - // Create the temporary file. - // Do blocking filesystem operations on a dedicated thread. + // Write the peer cache file atomically so the cache is not corrupted if Zebra shuts down + // or crashes. let span = Span::current(); - let tmp_peer_cache_file = tokio::task::spawn_blocking(move || { - span.in_scope(move || { - // Put the temporary file in the same directory as the permanent file, - // so atomic filesystem operations are possible. - tempfile::Builder::new() - .prefix(&tmp_peer_cache_prefix) - .tempfile_in(peer_cache_dir) - }) + let write_result = tokio::task::spawn_blocking(move || { + span.in_scope(move || atomic_write(peer_cache_file, peer_data.as_bytes())) }) .await - .expect("unexpected panic creating temporary peer cache file")?; - - // Write the list to the file asynchronously, by extracting the inner file, using it, - // then combining it back into a type that will correctly drop the file on error. - let (tmp_peer_cache_file, tmp_peer_cache_path) = tmp_peer_cache_file.into_parts(); - let mut tmp_peer_cache_file = tokio::fs::File::from_std(tmp_peer_cache_file); - tmp_peer_cache_file.write_all(peer_data.as_bytes()).await?; - - let tmp_peer_cache_file = - NamedTempFile::from_parts(tmp_peer_cache_file, tmp_peer_cache_path); - - // Atomically replace the current cache with the temporary cache. - // Do blocking filesystem operations on a dedicated thread. - let span = Span::current(); - tokio::task::spawn_blocking(move || { - span.in_scope(move || { - let result = tmp_peer_cache_file.persist(&peer_cache_file); - - // Drops the temp file if needed - match result { - Ok(_temp_file) => { - info!( - cached_ip_count = ?peer_list.len(), - ?peer_cache_file, - "updated cached peer IP addresses" - ); + .expect("could not write the peer cache file")?; + + match write_result { + Ok(peer_cache_file) => { + info!( + cached_ip_count = ?peer_list.len(), + ?peer_cache_file, + "updated cached peer IP addresses" + ); - for ip in &peer_list { - metrics::counter!( - "zcash.net.peers.cache", - "cache" => peer_cache_file.display().to_string(), - "remote_ip" => ip.to_string() - ) - .increment(1); - } - - Ok(()) - } - Err(error) => Err(error.error), + for ip in &peer_list { + metrics::counter!( + "zcash.net.peers.cache", + "cache" => peer_cache_file.display().to_string(), + "remote_ip" => ip.to_string() + ) + .increment(1); } - }) - }) - .await - .expect("unexpected panic making temporary peer cache file permanent") + + Ok(()) + } + Err(error) => Err(error.error), + } } } diff --git a/zebra-network/src/config/cache_dir.rs b/zebra-network/src/config/cache_dir.rs index 99b75f9f4e1..1e80b27bb9a 100644 --- a/zebra-network/src/config/cache_dir.rs +++ b/zebra-network/src/config/cache_dir.rs @@ -2,7 +2,7 @@ use std::path::{Path, PathBuf}; -use zebra_chain::parameters::Network; +use zebra_chain::{common::default_cache_dir, parameters::Network}; /// A cache directory config field. /// @@ -56,12 +56,7 @@ impl CacheDir { /// Returns the `zebra-network` base cache directory, if enabled. pub fn cache_dir(&self) -> Option { match self { - Self::IsEnabled(is_enabled) => is_enabled.then(|| { - dirs::cache_dir() - .unwrap_or_else(|| std::env::current_dir().unwrap().join("cache")) - .join("zebra") - }), - + Self::IsEnabled(is_enabled) => is_enabled.then(default_cache_dir), Self::CustomPath(cache_dir) => Some(cache_dir.to_owned()), } } diff --git a/zebra-state/src/config.rs b/zebra-state/src/config.rs index fa9032edf6e..4cd800f3975 100644 --- a/zebra-state/src/config.rs +++ b/zebra-state/src/config.rs @@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize}; use tokio::task::{spawn_blocking, JoinHandle}; use tracing::Span; -use zebra_chain::parameters::Network; +use zebra_chain::{common::default_cache_dir, parameters::Network}; use crate::{ constants::{DATABASE_FORMAT_VERSION_FILE_NAME, RESTORABLE_DB_VERSIONS, STATE_DATABASE_KIND}, @@ -173,12 +173,8 @@ impl Config { impl Default for Config { fn default() -> Self { - let cache_dir = dirs::cache_dir() - .unwrap_or_else(|| std::env::current_dir().unwrap().join("cache")) - .join("zebra"); - Self { - cache_dir, + cache_dir: default_cache_dir(), ephemeral: false, delete_old_database: true, debug_stop_at_height: None, @@ -471,6 +467,8 @@ pub(crate) use hidden::{ pub(crate) mod hidden { #![allow(dead_code)] + use zebra_chain::common::atomic_write; + use super::*; /// Writes `changed_version` to the on-disk state database after the format is changed. @@ -512,10 +510,9 @@ pub(crate) mod hidden { let version = format!("{}.{}", changed_version.minor, changed_version.patch); - // # Concurrency - // - // The caller handles locking for this file write. - fs::write(version_path, version.as_bytes())?; + // Write the version file atomically so the cache is not corrupted if Zebra shuts down or + // crashes. + atomic_write(version_path, version.as_bytes())??; Ok(()) } From 6b95d271d834d0ffe0bcc44bbed3f0d32fee0ed8 Mon Sep 17 00:00:00 2001 From: Arya Date: Fri, 30 Aug 2024 16:09:10 -0400 Subject: [PATCH 03/15] fix(rpc): Return verification errors from `sendrawtransaction` RPC method (#8788) * Adds a mempool request to wait for a transaction verification result and uses it in `sendrawtransaction` RPC method * removes unnecessary clone * fix clippy warnings * returns verification errors for all `mempool::Queue` requests, removes `QueueRpc` request variant * returns oneshot channel in mempool::Response::Queue * updates a test vector to check for download or verification error in mempool::response::Queued result receiver * Always require tokio as a dependency in zebra-node-services * checks for closed channel errors in sendrawtransaction and updates a prop test to check that verification errors are propagated correctly --- zebra-node-services/Cargo.toml | 4 +- zebra-node-services/src/mempool.rs | 9 ++-- zebra-rpc/src/methods.rs | 15 +++++-- zebra-rpc/src/methods/tests/prop.rs | 43 ++++++++++++++++--- zebra-rpc/src/queue/tests/prop.rs | 10 +++-- zebrad/src/components/mempool.rs | 36 ++++++++++------ .../components/mempool/crawler/tests/prop.rs | 14 ++++-- zebrad/src/components/mempool/downloads.rs | 31 +++++++++---- zebrad/src/components/mempool/tests/vector.rs | 17 +++++++- 9 files changed, 133 insertions(+), 46 deletions(-) diff --git a/zebra-node-services/Cargo.toml b/zebra-node-services/Cargo.toml index 5c188b34b40..8d0992dcf5a 100644 --- a/zebra-node-services/Cargo.toml +++ b/zebra-node-services/Cargo.toml @@ -34,7 +34,7 @@ rpc-client = [ "serde_json", ] -shielded-scan = ["tokio"] +shielded-scan = [] [dependencies] zebra-chain = { path = "../zebra-chain" , version = "1.0.0-beta.39" } @@ -48,7 +48,7 @@ jsonrpc-core = { version = "18.0.0", optional = true } reqwest = { version = "0.11.26", default-features = false, features = ["rustls-tls"], optional = true } serde = { version = "1.0.204", optional = true } serde_json = { version = "1.0.122", optional = true } -tokio = { version = "1.39.2", features = ["time"], optional = true } +tokio = { version = "1.39.2", features = ["time", "sync"] } [dev-dependencies] diff --git a/zebra-node-services/src/mempool.rs b/zebra-node-services/src/mempool.rs index 98c1969bbad..fbaaf029c75 100644 --- a/zebra-node-services/src/mempool.rs +++ b/zebra-node-services/src/mempool.rs @@ -4,6 +4,7 @@ use std::collections::HashSet; +use tokio::sync::oneshot; use zebra_chain::transaction::{self, UnminedTx, UnminedTxId}; #[cfg(feature = "getblocktemplate-rpcs")] @@ -114,13 +115,11 @@ pub enum Response { /// Returns matching cached rejected [`UnminedTxId`]s from the mempool, RejectedTransactionIds(HashSet), - /// Returns a list of queue results. - /// - /// These are the results of the initial queue checks. - /// The transaction may also fail download or verification later. + /// Returns a list of initial queue checks results and a oneshot receiver + /// for awaiting download and/or verification results. /// /// Each result matches the request at the corresponding vector index. - Queued(Vec>), + Queued(Vec>, BoxError>>), /// Confirms that the mempool has checked for recently verified transactions. CheckedForVerifiedTransactions, diff --git a/zebra-rpc/src/methods.rs b/zebra-rpc/src/methods.rs index ae5deb7a5b9..471d542922c 100644 --- a/zebra-rpc/src/methods.rs +++ b/zebra-rpc/src/methods.rs @@ -664,7 +664,7 @@ where let response = mempool.oneshot(request).await.map_server_error()?; - let queue_results = match response { + let mut queue_results = match response { mempool::Response::Queued(results) => results, _ => unreachable!("incorrect response variant from mempool service"), }; @@ -675,10 +675,17 @@ where "mempool service returned more results than expected" ); - tracing::debug!("sent transaction to mempool: {:?}", &queue_results[0]); + let queue_result = queue_results + .pop() + .expect("there should be exactly one item in Vec") + .inspect_err(|err| tracing::debug!("sent transaction to mempool: {:?}", &err)) + .map_server_error()? + .await; + + tracing::debug!("sent transaction to mempool: {:?}", &queue_result); - queue_results[0] - .as_ref() + queue_result + .map_server_error()? .map(|_| SentTransactionHash(transaction_hash)) .map_server_error() } diff --git a/zebra-rpc/src/methods/tests/prop.rs b/zebra-rpc/src/methods/tests/prop.rs index c2a9c70a348..409a6aefe52 100644 --- a/zebra-rpc/src/methods/tests/prop.rs +++ b/zebra-rpc/src/methods/tests/prop.rs @@ -7,6 +7,7 @@ use hex::ToHex; use jsonrpc_core::{Error, ErrorCode}; use proptest::{collection::vec, prelude::*}; use thiserror::Error; +use tokio::sync::oneshot; use tower::buffer::Buffer; use zebra_chain::{ @@ -61,7 +62,9 @@ proptest! { let unmined_transaction = UnminedTx::from(transaction); let expected_request = mempool::Request::Queue(vec![unmined_transaction.into()]); - let response = mempool::Response::Queued(vec![Ok(())]); + let (rsp_tx, rsp_rx) = oneshot::channel(); + let _ = rsp_tx.send(Ok(())); + let response = mempool::Response::Queued(vec![Ok(rsp_rx)]); mempool .expect_request(expected_request) @@ -111,10 +114,10 @@ proptest! { .expect("Transaction serializes successfully"); let transaction_hex = hex::encode(&transaction_bytes); - let send_task = tokio::spawn(rpc.send_raw_transaction(transaction_hex)); + let send_task = tokio::spawn(rpc.send_raw_transaction(transaction_hex.clone())); let unmined_transaction = UnminedTx::from(transaction); - let expected_request = mempool::Request::Queue(vec![unmined_transaction.into()]); + let expected_request = mempool::Request::Queue(vec![unmined_transaction.clone().into()]); mempool .expect_request(expected_request) @@ -138,6 +141,32 @@ proptest! { "Result is not a server error: {result:?}" ); + let send_task = tokio::spawn(rpc.send_raw_transaction(transaction_hex)); + + let expected_request = mempool::Request::Queue(vec![unmined_transaction.clone().into()]); + + let (rsp_tx, rsp_rx) = oneshot::channel(); + let _ = rsp_tx.send(Err("any verification error".into())); + mempool + .expect_request(expected_request) + .await? + .respond(Ok::<_, BoxError>(mempool::Response::Queued(vec![Ok(rsp_rx)]))); + + let result = send_task + .await + .expect("Sending raw transactions should not panic"); + + prop_assert!( + matches!( + result, + Err(Error { + code: ErrorCode::ServerError(_), + .. + }) + ), + "Result is not a server error: {result:?}" + ); + // The queue task should continue without errors or panics let rpc_tx_queue_task_result = rpc_tx_queue_task_handle.now_or_never(); prop_assert!(rpc_tx_queue_task_result.is_none()); @@ -897,7 +926,9 @@ proptest! { // now a retry will be sent to the mempool let expected_request = mempool::Request::Queue(vec![mempool::Gossip::Tx(tx_unmined.clone())]); - let response = mempool::Response::Queued(vec![Ok(())]); + let (rsp_tx, rsp_rx) = oneshot::channel(); + let _ = rsp_tx.send(Ok(())); + let response = mempool::Response::Queued(vec![Ok(rsp_rx)]); mempool .expect_request(expected_request) @@ -997,7 +1028,9 @@ proptest! { for tx in txs.clone() { let expected_request = mempool::Request::Queue(vec![mempool::Gossip::Tx(UnminedTx::from(tx))]); - let response = mempool::Response::Queued(vec![Ok(())]); + let (rsp_tx, rsp_rx) = oneshot::channel(); + let _ = rsp_tx.send(Ok(())); + let response = mempool::Response::Queued(vec![Ok(rsp_rx)]); mempool .expect_request(expected_request) diff --git a/zebra-rpc/src/queue/tests/prop.rs b/zebra-rpc/src/queue/tests/prop.rs index 1db9a340f2e..9f63ecce24d 100644 --- a/zebra-rpc/src/queue/tests/prop.rs +++ b/zebra-rpc/src/queue/tests/prop.rs @@ -5,7 +5,7 @@ use std::{collections::HashSet, env, sync::Arc}; use proptest::prelude::*; use chrono::Duration; -use tokio::time; +use tokio::{sync::oneshot, time}; use tower::ServiceExt; use zebra_chain::{ @@ -196,7 +196,9 @@ proptest! { let request = Request::Queue(vec![Gossip::Tx(unmined_transaction.clone())]); let expected_request = Request::Queue(vec![Gossip::Tx(unmined_transaction.clone())]); let send_task = tokio::spawn(mempool.clone().oneshot(request)); - let response = Response::Queued(vec![Ok(())]); + let (rsp_tx, rsp_rx) = oneshot::channel(); + let _ = rsp_tx.send(Ok(())); + let response = Response::Queued(vec![Ok(rsp_rx)]); mempool .expect_request(expected_request) @@ -337,7 +339,9 @@ proptest! { // retry will queue the transaction to mempool let gossip = Gossip::Tx(UnminedTx::from(transaction.clone())); let expected_request = Request::Queue(vec![gossip]); - let response = Response::Queued(vec![Ok(())]); + let (rsp_tx, rsp_rx) = oneshot::channel(); + let _ = rsp_tx.send(Ok(())); + let response = Response::Queued(vec![Ok(rsp_rx)]); mempool .expect_request(expected_request) diff --git a/zebrad/src/components/mempool.rs b/zebrad/src/components/mempool.rs index 2d9b2b3e0c5..05732ddaac2 100644 --- a/zebrad/src/components/mempool.rs +++ b/zebrad/src/components/mempool.rs @@ -27,7 +27,7 @@ use std::{ }; use futures::{future::FutureExt, stream::Stream}; -use tokio::sync::broadcast; +use tokio::sync::{broadcast, oneshot}; use tokio_stream::StreamExt; use tower::{buffer::Buffer, timeout::Timeout, util::BoxService, Service}; @@ -560,7 +560,7 @@ impl Service for Mempool { for tx in tx_retries { // This is just an efficiency optimisation, so we don't care if queueing // transaction requests fails. - let _result = tx_downloads.download_if_needed_and_verify(tx); + let _result = tx_downloads.download_if_needed_and_verify(tx, None); } } @@ -608,8 +608,8 @@ impl Service for Mempool { tracing::trace!("chain grew during tx verification, retrying ..",); // We don't care if re-queueing the transaction request fails. - let _result = - tx_downloads.download_if_needed_and_verify(tx.transaction.into()); + let _result = tx_downloads + .download_if_needed_and_verify(tx.transaction.into(), None); } } Ok(Err((txid, error))) => { @@ -758,16 +758,24 @@ impl Service for Mempool { Request::Queue(gossiped_txs) => { trace!(req_count = ?gossiped_txs.len(), "got mempool Queue request"); - let rsp: Vec> = gossiped_txs - .into_iter() - .map(|gossiped_tx| -> Result<(), MempoolError> { - storage.should_download_or_verify(gossiped_tx.id())?; - tx_downloads.download_if_needed_and_verify(gossiped_tx)?; - - Ok(()) - }) - .map(|result| result.map_err(BoxError::from)) - .collect(); + let rsp: Vec>, BoxError>> = + gossiped_txs + .into_iter() + .map( + |gossiped_tx| -> Result< + oneshot::Receiver>, + MempoolError, + > { + let (rsp_tx, rsp_rx) = oneshot::channel(); + storage.should_download_or_verify(gossiped_tx.id())?; + tx_downloads + .download_if_needed_and_verify(gossiped_tx, Some(rsp_tx))?; + + Ok(rsp_rx) + }, + ) + .map(|result| result.map_err(BoxError::from)) + .collect(); // We've added transactions to the queue self.update_metrics(); diff --git a/zebrad/src/components/mempool/crawler/tests/prop.rs b/zebrad/src/components/mempool/crawler/tests/prop.rs index fa1e3ef5785..524d754cfdc 100644 --- a/zebrad/src/components/mempool/crawler/tests/prop.rs +++ b/zebrad/src/components/mempool/crawler/tests/prop.rs @@ -6,7 +6,7 @@ use proptest::{ collection::{hash_set, vec}, prelude::*, }; -use tokio::time; +use tokio::{sync::oneshot, time}; use zebra_chain::{ chain_sync_status::ChainSyncStatus, parameters::Network, transaction::UnminedTxId, @@ -317,9 +317,17 @@ async fn respond_to_queue_request( expected_transaction_ids: HashSet, response: impl IntoIterator>, ) -> Result<(), TestCaseError> { - let response = response + let response: Vec>, BoxError>> = response .into_iter() - .map(|result| result.map_err(BoxError::from)) + .map(|result| { + result + .map(|_| { + let (rsp_tx, rsp_rx) = oneshot::channel(); + let _ = rsp_tx.send(Ok(())); + rsp_rx + }) + .map_err(BoxError::from) + }) .collect(); mempool diff --git a/zebrad/src/components/mempool/downloads.rs b/zebrad/src/components/mempool/downloads.rs index d3f62b4087b..b37f988dcc8 100644 --- a/zebrad/src/components/mempool/downloads.rs +++ b/zebrad/src/components/mempool/downloads.rs @@ -51,7 +51,7 @@ use zebra_chain::{ use zebra_consensus::transaction as tx; use zebra_network as zn; use zebra_node_services::mempool::Gossip; -use zebra_state as zs; +use zebra_state::{self as zs, CloneError}; use crate::components::sync::{BLOCK_DOWNLOAD_TIMEOUT, BLOCK_VERIFY_TIMEOUT}; @@ -105,17 +105,17 @@ pub const MAX_INBOUND_CONCURRENCY: usize = 25; struct CancelDownloadAndVerify; /// Errors that can occur while downloading and verifying a transaction. -#[derive(Error, Debug)] +#[derive(Error, Debug, Clone)] #[allow(dead_code)] pub enum TransactionDownloadVerifyError { #[error("transaction is already in state")] InState, #[error("error in state service")] - StateError(#[source] BoxError), + StateError(#[source] CloneError), #[error("error downloading transaction")] - DownloadFailed(#[source] BoxError), + DownloadFailed(#[source] CloneError), #[error("transaction download / verification was cancelled")] Cancelled, @@ -243,6 +243,7 @@ where pub fn download_if_needed_and_verify( &mut self, gossiped_tx: Gossip, + rsp_tx: Option>>, ) -> Result<(), MempoolError> { let txid = gossiped_tx.id(); @@ -295,7 +296,7 @@ where Ok((Some(height), next_height)) } Ok(_) => unreachable!("wrong response"), - Err(e) => Err(TransactionDownloadVerifyError::StateError(e)), + Err(e) => Err(TransactionDownloadVerifyError::StateError(e.into())), }?; trace!(?txid, ?next_height, "got next height"); @@ -307,11 +308,12 @@ where let tx = match network .oneshot(req) .await + .map_err(CloneError::from) .map_err(TransactionDownloadVerifyError::DownloadFailed)? { zn::Response::Transactions(mut txs) => txs.pop().ok_or_else(|| { TransactionDownloadVerifyError::DownloadFailed( - "no transactions returned".into(), + BoxError::from("no transactions returned").into(), ) })?, _ => unreachable!("wrong response to transaction request"), @@ -373,7 +375,7 @@ where let task = tokio::spawn(async move { // Prefer the cancel handle if both are ready. - tokio::select! { + let result = tokio::select! { biased; _ = &mut cancel_rx => { trace!("task cancelled prior to completion"); @@ -381,7 +383,19 @@ where Err((TransactionDownloadVerifyError::Cancelled, txid)) } verification = fut => verification, + }; + + // Send the result to responder channel if one was provided. + if let Some(rsp_tx) = rsp_tx { + let _ = rsp_tx.send( + result + .as_ref() + .map(|_| ()) + .map_err(|(err, _)| err.clone().into()), + ); } + + result }); self.pending.push(task); @@ -458,6 +472,7 @@ where match state .ready() .await + .map_err(CloneError::from) .map_err(TransactionDownloadVerifyError::StateError)? .call(zs::Request::Transaction(txid.mined_id())) .await @@ -465,7 +480,7 @@ where Ok(zs::Response::Transaction(None)) => Ok(()), Ok(zs::Response::Transaction(Some(_))) => Err(TransactionDownloadVerifyError::InState), Ok(_) => unreachable!("wrong response"), - Err(e) => Err(TransactionDownloadVerifyError::StateError(e)), + Err(e) => Err(TransactionDownloadVerifyError::StateError(e.into())), }?; Ok(()) diff --git a/zebrad/src/components/mempool/tests/vector.rs b/zebrad/src/components/mempool/tests/vector.rs index 2868fef2e65..c285923fa7d 100644 --- a/zebrad/src/components/mempool/tests/vector.rs +++ b/zebrad/src/components/mempool/tests/vector.rs @@ -445,12 +445,17 @@ async fn mempool_cancel_mined() -> Result<(), Report> { .call(Request::Queue(vec![txid.into()])) .await .unwrap(); - let queued_responses = match response { + let mut queued_responses = match response { Response::Queued(queue_responses) => queue_responses, _ => unreachable!("will never happen in this test"), }; assert_eq!(queued_responses.len(), 1); - assert!(queued_responses[0].is_ok()); + + let queued_response = queued_responses + .pop() + .expect("already checked that there is exactly 1 item in Vec") + .expect("initial queue checks result should be Ok"); + assert_eq!(mempool.tx_downloads().in_flight(), 1); // Push block 2 to the state @@ -489,6 +494,14 @@ async fn mempool_cancel_mined() -> Result<(), Report> { // Check if download was cancelled. assert_eq!(mempool.tx_downloads().in_flight(), 0); + assert!( + queued_response + .await + .expect("channel should not be closed") + .is_err(), + "queued tx should fail to download and verify due to chain tip change" + ); + Ok(()) } From 9a616f46a5087e8598eb0e5e6d657b0206907fd2 Mon Sep 17 00:00:00 2001 From: Marek Date: Mon, 2 Sep 2024 21:00:26 +0200 Subject: [PATCH 04/15] fix(deps): Replace `serde_yaml` by `serde_yml` (#8825) * Use `serde_yml` instead of `serde_yaml` * Regenerate `openapi.yaml` --- Cargo.lock | 32 +++++++----- openapi.yaml | 52 +++++++++---------- supply-chain/config.toml | 5 -- zebra-utils/Cargo.toml | 4 +- zebra-utils/src/bin/openapi-generator/main.rs | 4 +- 5 files changed, 49 insertions(+), 48 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c5169cf98dc..ae84f840965 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2434,6 +2434,16 @@ dependencies = [ "lz4-sys", ] +[[package]] +name = "libyml" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3302702afa434ffa30847a83305f0a69d6abd74293b6554c18ec85c7ef30c980" +dependencies = [ + "anyhow", + "version_check", +] + [[package]] name = "libz-sys" version = "1.1.18" @@ -4071,16 +4081,18 @@ dependencies = [ ] [[package]] -name = "serde_yaml" -version = "0.9.34+deprecated" +name = "serde_yml" +version = "0.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +checksum = "59e2dd588bf1597a252c3b920e0143eb99b0f76e4e082f4c92ce34fbc9e71ddd" dependencies = [ "indexmap 2.3.0", "itoa", + "libyml", + "memchr", "ryu", "serde", - "unsafe-libyaml", + "version_check", ] [[package]] @@ -5018,12 +5030,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "unsafe-libyaml" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" - [[package]] name = "untrusted" version = "0.9.0" @@ -5108,9 +5114,9 @@ dependencies = [ [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "visibility" @@ -6318,7 +6324,7 @@ dependencies = [ "reqwest", "serde", "serde_json", - "serde_yaml", + "serde_yml", "structopt", "syn 2.0.72", "thiserror", diff --git a/openapi.yaml b/openapi.yaml index 2a7636a90b7..10ac6bfdf61 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -28,7 +28,7 @@ paths: default: getinfo id: type: string - default: x2r3lRddGL + default: VuJXrxLSw8 params: type: array items: {} @@ -61,7 +61,7 @@ paths: default: getblockchaininfo id: type: string - default: w8Lb0nAvLd + default: HDVqYXM9m6 params: type: array items: {} @@ -99,7 +99,7 @@ paths: default: getaddressbalance id: type: string - default: QbTztoTvRo + default: Xw5TDBKXGl params: type: array items: {} @@ -147,7 +147,7 @@ paths: default: sendrawtransaction id: type: string - default: aDK5RQWj16 + default: QaJv2bXyZu params: type: array items: {} @@ -196,7 +196,7 @@ paths: default: getblock id: type: string - default: xxCP1d61X0 + default: k0DACJrgZs params: type: array items: {} @@ -239,7 +239,7 @@ paths: default: getbestblockhash id: type: string - default: DoZgd1j7xW + default: rIFaLhZwHF params: type: array items: {} @@ -272,7 +272,7 @@ paths: default: getbestblockheightandhash id: type: string - default: 0iUFHsOjk3 + default: oxrhh1swvh params: type: array items: {} @@ -305,7 +305,7 @@ paths: default: getrawmempool id: type: string - default: WXG2c6FcCK + default: E7oUD34jk2 params: type: array items: {} @@ -343,7 +343,7 @@ paths: default: z_gettreestate id: type: string - default: 38P0xXV0do + default: Hp22XK728i params: type: array items: {} @@ -393,7 +393,7 @@ paths: default: z_getsubtreesbyindex id: type: string - default: 662iR8VZGT + default: Cs69hg68pl params: type: array items: {} @@ -432,7 +432,7 @@ paths: default: getrawtransaction id: type: string - default: UuvVrzSzqC + default: iu395PEErc params: type: array items: {} @@ -480,7 +480,7 @@ paths: default: getaddresstxids id: type: string - default: KMss2wDMwH + default: z3lOKfsQdp params: type: array items: {} @@ -528,7 +528,7 @@ paths: default: getaddressutxos id: type: string - default: 4Y6BAhe6Lf + default: '7U4Q4dSxej' params: type: array items: {} @@ -571,7 +571,7 @@ paths: default: getblockcount id: type: string - default: nzPm5W3X1G + default: '8yw3EX7Cwi' params: type: array items: {} @@ -609,7 +609,7 @@ paths: default: getblockhash id: type: string - default: KLKosq2Z8E + default: ndDYksCl9E params: type: array items: {} @@ -657,7 +657,7 @@ paths: default: getblocktemplate id: type: string - default: spj7gKe2AA + default: lJi2hfxty1 params: type: array items: {} @@ -695,7 +695,7 @@ paths: default: submitblock id: type: string - default: QOQsC3nA7z + default: '9fEFOdTQle' params: type: array items: {} @@ -728,7 +728,7 @@ paths: default: getmininginfo id: type: string - default: Si3Sdb9ICT + default: Dytpq4f3lF params: type: array items: {} @@ -761,7 +761,7 @@ paths: default: getnetworksolps id: type: string - default: jWvKPdOxDa + default: yX3woRnOaN params: type: array items: {} @@ -794,7 +794,7 @@ paths: default: getnetworkhashps id: type: string - default: wnFwBVFrN0 + default: AyAZMtbezv params: type: array items: {} @@ -827,7 +827,7 @@ paths: default: getpeerinfo id: type: string - default: NpKiq59CE8 + default: nNcrsu3ZAR params: type: array items: {} @@ -865,7 +865,7 @@ paths: default: validateaddress id: type: string - default: PDjTChWgFW + default: LGyfO7zTjW params: type: array items: {} @@ -903,7 +903,7 @@ paths: default: z_validateaddress id: type: string - default: aCeb6xbIuo + default: '2Q09a2Nh4N' params: type: array items: {} @@ -941,7 +941,7 @@ paths: default: getblocksubsidy id: type: string - default: EeBvVXCJon + default: nv6lOWCRso params: type: array items: {} @@ -984,7 +984,7 @@ paths: default: getdifficulty id: type: string - default: jg2K8N0ZG4 + default: '2O3A0PF1SS' params: type: array items: {} @@ -1022,7 +1022,7 @@ paths: default: z_listunifiedreceivers id: type: string - default: Y3gscsg8yT + default: XYgGcDIx2X params: type: array items: {} diff --git a/supply-chain/config.toml b/supply-chain/config.toml index 9ca2020fc37..21bfeebddba 100644 --- a/supply-chain/config.toml +++ b/supply-chain/config.toml @@ -1,4 +1,3 @@ - # cargo-vet config file [cargo-vet] @@ -1414,10 +1413,6 @@ criteria = "safe-to-deploy" version = "3.8.1" criteria = "safe-to-deploy" -[[exemptions.serde_yaml]] -version = "0.9.34+deprecated" -criteria = "safe-to-deploy" - [[exemptions.sha2]] version = "0.10.8" criteria = "safe-to-deploy" diff --git a/zebra-utils/Cargo.toml b/zebra-utils/Cargo.toml index 1c873400730..2c1ce74992e 100644 --- a/zebra-utils/Cargo.toml +++ b/zebra-utils/Cargo.toml @@ -77,7 +77,7 @@ openapi-generator = [ "zebra-rpc", "syn", "quote", - "serde_yaml", + "serde_yml", "serde" ] @@ -121,7 +121,7 @@ zcash_protocol.workspace = true rand = "0.8.5" syn = { version = "2.0.72", features = ["full"], optional = true } quote = { version = "1.0.36", optional = true } -serde_yaml = { version = "0.9.34+deprecated", optional = true } +serde_yml = { version = "0.0.12", optional = true } serde = { version = "1.0.204", features = ["serde_derive"], optional = true } indexmap = "2.3.0" diff --git a/zebra-utils/src/bin/openapi-generator/main.rs b/zebra-utils/src/bin/openapi-generator/main.rs index 0935f6560ff..fd0e9fe9b2b 100644 --- a/zebra-utils/src/bin/openapi-generator/main.rs +++ b/zebra-utils/src/bin/openapi-generator/main.rs @@ -174,9 +174,9 @@ fn main() -> Result<(), Box> { let all_methods = Methods { paths: methods }; // Add openapi header and write to file - let yaml_string = serde_yaml::to_string(&all_methods)?; + let yml_string = serde_yml::to_string(&all_methods)?; let mut w = File::create("openapi.yaml")?; - w.write_all(format!("{}{}", create_yaml(), yaml_string).as_bytes())?; + w.write_all(format!("{}{}", create_yaml(), yml_string).as_bytes())?; Ok(()) } From 5541b27a12df19b9b0e21ca5e5c37de6091170af Mon Sep 17 00:00:00 2001 From: Marek Date: Tue, 3 Sep 2024 12:04:36 +0200 Subject: [PATCH 05/15] Remove `shielded-scan` from experimental features (#8827) --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 1ccaa5915a5..a304934455a 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -146,7 +146,7 @@ COPY ./docker/entrypoint.sh /etc/zebrad/entrypoint.sh # Entrypoint environment variables ENV ENTRYPOINT_FEATURES=${ENTRYPOINT_FEATURES} # We repeat the ARGs here, so they are available in the entrypoint.sh script for $RUN_ALL_EXPERIMENTAL_TESTS -ARG EXPERIMENTAL_FEATURES="shielded-scan journald prometheus filter-reload" +ARG EXPERIMENTAL_FEATURES="journald prometheus filter-reload" ENV ENTRYPOINT_FEATURES_EXPERIMENTAL="${ENTRYPOINT_FEATURES} ${EXPERIMENTAL_FEATURES}" # By default, runs the entrypoint tests specified by the environmental variables (if any are set) From f2427d62e68a635654f692d8849a32d3fc0e47b5 Mon Sep 17 00:00:00 2001 From: Marek Date: Tue, 3 Sep 2024 15:43:51 +0200 Subject: [PATCH 06/15] add(docs): Add minimal hardware requirements (#8822) * clean-up: Remove outdated note * Add minimal hardware requirements Source & credit: https://x.com/Zerodartz/status/1811460885996798159 * Apply suggestions from code review Co-authored-by: Arya --------- Co-authored-by: Arya Co-authored-by: Pili Guerra --- book/src/user/requirements.md | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/book/src/user/requirements.md b/book/src/user/requirements.md index df95aa139ba..d908a7487de 100644 --- a/book/src/user/requirements.md +++ b/book/src/user/requirements.md @@ -1,16 +1,22 @@ # System Requirements -We recommend the following requirements for compiling and running `zebrad`: +Zebra has the following hardware requirements. + +## Recommended Requirements - 4 CPU cores - 16 GB RAM -- 300 GB available disk space for building binaries and storing cached chain - state +- 300 GB available disk space - 100 Mbps network connection, with 300 GB of uploads and downloads per month -Zebra's tests can take over an hour, depending on your machine. Note that you -might be able to build and run Zebra on slower systems — we haven't tested its -exact limits yet. +## Minimum Hardware Requirements + +- 2 CPU cores +- 4 GB RAM +- 300 GB available disk space + +[Zebra has successfully run on an Orange Pi Zero 2W with a 512 GB microSD card +without any issues.](https://x.com/Zerodartz/status/1811460885996798159) ## Disk Requirements @@ -48,9 +54,6 @@ networks. - Ongoing updates: 10 MB - 10 GB upload and download per day, depending on user-created transaction size and peer requests. -Zebra performs an initial sync every time its internal database version changes, -so some version upgrades might require a full download of the whole chain. - Zebra needs some peers which have a round-trip latency of 2 seconds or less. If this is a problem for you, please [open a ticket.](https://github.com/ZcashFoundation/zebra/issues/new/choose) From e9bbb9747360f6bf3f9180df85f8563aebd5ae68 Mon Sep 17 00:00:00 2001 From: dismad <81990132+dismad@users.noreply.github.com> Date: Tue, 3 Sep 2024 06:43:57 -0700 Subject: [PATCH 07/15] Update README.md (#8824) typo --- zebra-utils/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zebra-utils/README.md b/zebra-utils/README.md index 48a887e09dd..2422264ea4a 100644 --- a/zebra-utils/README.md +++ b/zebra-utils/README.md @@ -112,7 +112,7 @@ This program is commonly used as part of `zebrad-log-filter` where hashes will b The program is designed to filter the output from the zebra terminal or log file. Each time a hash is seen the script will capture it and get the additional information using `zebrad-hash-lookup`. -Assuming `zebrad`, `zclash-cli`, `zebrad-hash-lookup` and `zebrad-log-filter` are in your path the program can used as: +Assuming `zebrad`, `zcash-cli`, `zebrad-hash-lookup` and `zebrad-log-filter` are in your path the program can used as: ```sh $ zebrad -v start | zebrad-log-filter From d31eea5f6456966b6183548729d79b7187897b42 Mon Sep 17 00:00:00 2001 From: Gustavo Valverde Date: Thu, 5 Sep 2024 14:29:22 +0100 Subject: [PATCH 08/15] ref(docker): use cache mounts for build cache (#8796) * ref(docker): leverage cache mount with bind mounts This update eliminates the need for external tools like `cargo-chef` to leverage caching layers, resulting in an average build time reduction of 4m30s (~36% improvement). While this solution doesn't fully resolve the issues mentioned in https://github.com/ZcashFoundation/zebra/issues/6169#issuecomment-1712776391, it represents the best possible approach without resorting to custom solutions, which we'd prefer to avoid. * chore: remove extra `WORKDIR` and imp comments * chore: improve comment legibility Co-authored-by: Arya --------- Co-authored-by: Pili Guerra Co-authored-by: Arya --- docker/Dockerfile | 174 +++++++++++++++++++++------------------------- 1 file changed, 80 insertions(+), 94 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index a304934455a..c71e3e422f5 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,12 +1,13 @@ +# syntax=docker/dockerfile:1 +# check=skip=UndefinedVar + # If you want to include a file in the Docker image, add it to .dockerignore. # -# We are using five stages: -# - chef: installs cargo-chef -# - planner: computes the recipe file -# - deps: caches our dependencies and sets the needed variables -# - tests: builds tests -# - release: builds release binary -# - runtime: is our runtime environment +# We are using 4 stages: +# - deps: install build dependencies and sets the needed variables +# - tests: builds tests binaries +# - release: builds release binaries +# - runtime: runs the release binaries # # We first set default values for build arguments used across the stages. # Each stage must define the build arguments (ARGs) it uses. @@ -20,29 +21,18 @@ ARG TEST_FEATURES="lightwalletd-grpc-tests zebra-checkpoints" ARG EXPERIMENTAL_FEATURES="" ARG APP_HOME="/opt/zebrad" -# This stage implements cargo-chef for docker layer caching -FROM rust:bookworm as chef -RUN cargo install cargo-chef --locked - -ARG APP_HOME -ENV APP_HOME=${APP_HOME} -WORKDIR ${APP_HOME} - -# Analyze the current project to determine the minimum subset of files -# (Cargo.lock and Cargo.toml manifests) required to build it and cache dependencies -# -# The recipe.json is the equivalent of the Python requirements.txt file -FROM chef AS planner -COPY . . -RUN cargo chef prepare --recipe-path recipe.json - +ARG RUST_VERSION=1.79.0 # In this stage we download all system requirements to build the project # # It also captures all the build arguments to be used as environment variables. # We set defaults for the arguments, in case the build does not include this information. -FROM chef AS deps +FROM rust:${RUST_VERSION}-bookworm AS deps SHELL ["/bin/bash", "-xo", "pipefail", "-c"] -COPY --from=planner ${APP_HOME}/recipe.json recipe.json + +# Set the default path for the zebrad binary +ARG APP_HOME +ENV APP_HOME=${APP_HOME} +WORKDIR ${APP_HOME} # Install zebra build deps and Dockerfile deps RUN apt-get -qq update && \ @@ -52,27 +42,8 @@ RUN apt-get -qq update && \ clang \ ca-certificates \ protobuf-compiler \ - rsync \ rocksdb-tools \ - ; \ - rm -rf /var/lib/apt/lists/* /tmp/* - -# Install google OS Config agent to be able to get information from the VMs being deployed -# into GCP for integration testing purposes, and as Mainnet nodes -# TODO: this shouldn't be a hardcoded requirement for everyone -RUN if [ "$(uname -m)" != "aarch64" ]; then \ - apt-get -qq update && \ - apt-get -qq install -y --no-install-recommends \ - curl \ - lsb-release \ - && \ - echo "deb http://packages.cloud.google.com/apt google-compute-engine-$(lsb_release -cs)-stable main" > /etc/apt/sources.list.d/google-compute-engine.list && \ - curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - && \ - apt-get -qq update && \ - apt-get -qq install -y --no-install-recommends google-osconfig-agent; \ - fi \ - && \ - rm -rf /var/lib/apt/lists/* /tmp/* + && rm -rf /var/lib/apt/lists/* /tmp/* # Build arguments and variables set for tracelog levels and debug information # @@ -90,24 +61,21 @@ ARG COLORBT_SHOW_HIDDEN ENV COLORBT_SHOW_HIDDEN=${COLORBT_SHOW_HIDDEN:-1} ARG SHORT_SHA -# If this is not set, it must be the empty string, so Zebra can try an alternative git commit source: +# If this is not set, it must be an empty string, so Zebra can try an alternative git commit source: # https://github.com/ZcashFoundation/zebra/blob/9ebd56092bcdfc1a09062e15a0574c94af37f389/zebrad/src/application.rs#L179-L182 ENV SHORT_SHA=${SHORT_SHA:-} ENV CARGO_HOME="${APP_HOME}/.cargo/" +# Copy the entrypoint script to be used on both images +COPY ./docker/entrypoint.sh /etc/zebrad/entrypoint.sh + # In this stage we build tests (without running then) # # We also download needed dependencies for tests to work, from other images. # An entrypoint.sh is only available in this step for easier test handling with variables. FROM deps AS tests -COPY --from=electriccoinco/lightwalletd:latest /usr/local/bin/lightwalletd /usr/local/bin/ - -# cargo uses timestamps for its cache, so they need to be in this order: -# unmodified source files < previous build cache < modified source files -COPY . . - # Skip IPv6 tests by default, as some CI environment don't have IPv6 available ARG ZEBRA_SKIP_IPV6_TESTS ENV ZEBRA_SKIP_IPV6_TESTS=${ZEBRA_SKIP_IPV6_TESTS:-1} @@ -120,28 +88,41 @@ ARG EXPERIMENTAL_FEATURES # TODO: add empty $EXPERIMENTAL_FEATURES when we can avoid adding an extra space to the end of the string ARG ENTRYPOINT_FEATURES="${FEATURES} ${TEST_FEATURES}" -# Re-hydrate the minimum project skeleton identified by `cargo chef prepare` in the planner stage, -# over the top of the original source files, -# and build it to cache all possible sentry and test dependencies. -# -# This is the caching Docker layer for Rust tests! -# It creates fake empty test binaries so dependencies are built, but Zebra is not fully built. -# -# TODO: add --locked when cargo-chef supports it -RUN cargo chef cook --tests --release --features "${ENTRYPOINT_FEATURES}" --workspace --recipe-path recipe.json -# Undo the source file changes made by cargo-chef. -# rsync invalidates the cargo cache for the changed files only, by updating their timestamps. -# This makes sure the fake empty binaries created by cargo-chef are rebuilt. -COPY --from=planner ${APP_HOME} zebra-original -RUN rsync --recursive --checksum --itemize-changes --verbose zebra-original/ . -RUN rm -r zebra-original - # Build Zebra test binaries, but don't run them -RUN cargo test --locked --release --features "${ENTRYPOINT_FEATURES}" --workspace --no-run -RUN cp ${APP_HOME}/target/release/zebrad /usr/local/bin -RUN cp ${APP_HOME}/target/release/zebra-checkpoints /usr/local/bin -COPY ./docker/entrypoint.sh /etc/zebrad/entrypoint.sh +# Leverage a cache mount to /usr/local/cargo/registry/ +# for downloaded dependencies, a cache mount to /usr/local/cargo/git/db +# for git repository dependencies, and a cache mount to ${APP_HOME}/target/ for +# compiled dependencies which will speed up subsequent builds. +# Leverage a bind mount to each crate directory to avoid having to copy the +# source code into the container. Once built, copy the executable to an +# output directory before the cache mounted ${APP_HOME}/target/ is unmounted. +RUN --mount=type=bind,source=zebrad,target=zebrad \ + --mount=type=bind,source=zebra-chain,target=zebra-chain \ + --mount=type=bind,source=zebra-network,target=zebra-network \ + --mount=type=bind,source=zebra-state,target=zebra-state \ + --mount=type=bind,source=zebra-script,target=zebra-script \ + --mount=type=bind,source=zebra-consensus,target=zebra-consensus \ + --mount=type=bind,source=zebra-rpc,target=zebra-rpc \ + --mount=type=bind,source=zebra-node-services,target=zebra-node-services \ + --mount=type=bind,source=zebra-test,target=zebra-test \ + --mount=type=bind,source=zebra-utils,target=zebra-utils \ + --mount=type=bind,source=zebra-scan,target=zebra-scan \ + --mount=type=bind,source=zebra-grpc,target=zebra-grpc \ + --mount=type=bind,source=tower-batch-control,target=tower-batch-control \ + --mount=type=bind,source=tower-fallback,target=tower-fallback \ + --mount=type=bind,source=Cargo.toml,target=Cargo.toml \ + --mount=type=bind,source=Cargo.lock,target=Cargo.lock \ + --mount=type=cache,target=${APP_HOME}/target/ \ + --mount=type=cache,target=/usr/local/cargo/git/db \ + --mount=type=cache,target=/usr/local/cargo/registry/ \ +cargo test --locked --release --features "${ENTRYPOINT_FEATURES}" --workspace --no-run && \ +cp ${APP_HOME}/target/release/zebrad /usr/local/bin && \ +cp ${APP_HOME}/target/release/zebra-checkpoints /usr/local/bin + +# Copy the lightwalletd binary and source files to be able to run tests +COPY --from=electriccoinco/lightwalletd:latest /usr/local/bin/lightwalletd /usr/local/bin/ +COPY ./ ./ # Entrypoint environment variables ENV ENTRYPOINT_FEATURES=${ENTRYPOINT_FEATURES} @@ -154,30 +135,34 @@ ENTRYPOINT [ "/etc/zebrad/entrypoint.sh" ] # In this stage we build a release (generate the zebrad binary) # -# This step also adds `cargo chef` as this stage is completely independent from the +# This step also adds `cache mounts` as this stage is completely independent from the # `test` stage. This step is a dependency for the `runtime` stage, which uses the resulting # zebrad binary from this step. FROM deps AS release -COPY . . - ARG FEATURES -# This is the caching layer for Rust zebrad builds. -# It creates a fake empty zebrad binary, see above for details. -# -# TODO: add --locked when cargo-chef supports it -RUN cargo chef cook --release --features "${FEATURES}" --package zebrad --bin zebrad --recipe-path recipe.json - -# Undo the source file changes made by cargo-chef, so the fake empty zebrad binary is rebuilt. -COPY --from=planner ${APP_HOME} zebra-original -RUN rsync --recursive --checksum --itemize-changes --verbose zebra-original/ . -RUN rm -r zebra-original - -# Build zebrad -RUN cargo build --locked --release --features "${FEATURES}" --package zebrad --bin zebrad - -COPY ./docker/entrypoint.sh ./ +RUN --mount=type=bind,source=tower-batch-control,target=tower-batch-control \ + --mount=type=bind,source=tower-fallback,target=tower-fallback \ + --mount=type=bind,source=zebra-chain,target=zebra-chain \ + --mount=type=bind,source=zebra-consensus,target=zebra-consensus \ + --mount=type=bind,source=zebra-grpc,target=zebra-grpc \ + --mount=type=bind,source=zebra-network,target=zebra-network \ + --mount=type=bind,source=zebra-node-services,target=zebra-node-services \ + --mount=type=bind,source=zebra-rpc,target=zebra-rpc \ + --mount=type=bind,source=zebra-scan,target=zebra-scan \ + --mount=type=bind,source=zebra-script,target=zebra-script \ + --mount=type=bind,source=zebra-state,target=zebra-state \ + --mount=type=bind,source=zebra-test,target=zebra-test \ + --mount=type=bind,source=zebra-utils,target=zebra-utils \ + --mount=type=bind,source=zebrad,target=zebrad \ + --mount=type=bind,source=Cargo.toml,target=Cargo.toml \ + --mount=type=bind,source=Cargo.lock,target=Cargo.lock \ + --mount=type=cache,target=${APP_HOME}/target/ \ + --mount=type=cache,target=/usr/local/cargo/git/db \ + --mount=type=cache,target=/usr/local/cargo/registry/ \ +cargo build --locked --release --features "${FEATURES}" --package zebrad --bin zebrad && \ +cp ${APP_HOME}/target/release/zebrad /usr/local/bin # This stage is only used when deploying nodes or when only the resulting zebrad binary is needed # @@ -196,8 +181,7 @@ RUN apt-get update && \ curl \ rocksdb-tools \ gosu \ - && \ - rm -rf /var/lib/apt/lists/* /tmp/* + && rm -rf /var/lib/apt/lists/* /tmp/* # Create a non-privileged user that the app will run under. # Running as root inside the container is running as root in the Docker host @@ -215,6 +199,7 @@ RUN addgroup --system --gid ${GID} ${USER} \ --system \ --disabled-login \ --shell /bin/bash \ + --home ${APP_HOME} \ --uid "${UID}" \ --gid "${GID}" \ ${USER} @@ -224,14 +209,15 @@ ARG FEATURES ENV FEATURES=${FEATURES} # Path and name of the config file +# These are set to a default value when not defined in the environment ENV ZEBRA_CONF_DIR=${ZEBRA_CONF_DIR:-/etc/zebrad} ENV ZEBRA_CONF_FILE=${ZEBRA_CONF_FILE:-zebrad.toml} RUN mkdir -p ${ZEBRA_CONF_DIR} && chown ${UID}:${UID} ${ZEBRA_CONF_DIR} \ && chown ${UID}:${UID} ${APP_HOME} -COPY --from=release ${APP_HOME}/target/release/zebrad /usr/local/bin -COPY --from=release ${APP_HOME}/entrypoint.sh /etc/zebrad +COPY --from=release /usr/local/bin/zebrad /usr/local/bin +COPY --from=release /etc/zebrad/entrypoint.sh /etc/zebrad # Expose configured ports EXPOSE 8233 18233 From 17d7f914a8fae07fd3b63e05b4f35e18500d53d1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Sep 2024 18:28:11 +0000 Subject: [PATCH 09/15] build(deps): bump tj-actions/changed-files in the devops group (#8835) Bumps the devops group with 1 update: [tj-actions/changed-files](https://github.com/tj-actions/changed-files). Updates `tj-actions/changed-files` from 45.0.0 to 45.0.1 - [Release notes](https://github.com/tj-actions/changed-files/releases) - [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md) - [Commits](https://github.com/tj-actions/changed-files/compare/v45.0.0...v45.0.1) --- updated-dependencies: - dependency-name: tj-actions/changed-files dependency-type: direct:production update-type: version-update:semver-patch dependency-group: devops ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-lint.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-lint.yml b/.github/workflows/ci-lint.yml index 3137dee49db..87b60204746 100644 --- a/.github/workflows/ci-lint.yml +++ b/.github/workflows/ci-lint.yml @@ -44,7 +44,7 @@ jobs: - name: Rust files id: changed-files-rust - uses: tj-actions/changed-files@v45.0.0 + uses: tj-actions/changed-files@v45.0.1 with: files: | **/*.rs @@ -56,7 +56,7 @@ jobs: - name: Workflow files id: changed-files-workflows - uses: tj-actions/changed-files@v45.0.0 + uses: tj-actions/changed-files@v45.0.1 with: files: | .github/workflows/*.yml From 151199782912d085d1ab0bfc6fa68cd061e0e867 Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Fri, 6 Sep 2024 12:24:15 -0300 Subject: [PATCH 10/15] fix clippy lints (#8855) --- zebra-state/src/service/finalized_state/zebra_db/block.rs | 1 + zebrad/src/components/mempool/downloads.rs | 1 + zebrad/tests/common/checkpoints.rs | 2 +- zebrad/tests/common/get_block_template_rpcs/get_peer_info.rs | 2 +- zebrad/tests/common/get_block_template_rpcs/submit_block.rs | 2 +- zebrad/tests/common/lightwalletd/send_transaction_test.rs | 2 +- 6 files changed, 6 insertions(+), 4 deletions(-) diff --git a/zebra-state/src/service/finalized_state/zebra_db/block.rs b/zebra-state/src/service/finalized_state/zebra_db/block.rs index e10fd3b43b4..4dc3a801ef3 100644 --- a/zebra-state/src/service/finalized_state/zebra_db/block.rs +++ b/zebra-state/src/service/finalized_state/zebra_db/block.rs @@ -290,6 +290,7 @@ impl ZebraDb { /// /// - Propagates any errors from writing to the DB /// - Propagates any errors from updating history and note commitment trees + #[allow(clippy::unwrap_in_result)] pub(in super::super) fn write_block( &mut self, finalized: FinalizedBlock, diff --git a/zebrad/src/components/mempool/downloads.rs b/zebrad/src/components/mempool/downloads.rs index b37f988dcc8..eeda6bd9567 100644 --- a/zebrad/src/components/mempool/downloads.rs +++ b/zebrad/src/components/mempool/downloads.rs @@ -240,6 +240,7 @@ where /// /// Returns the action taken in response to the queue request. #[instrument(skip(self, gossiped_tx), fields(txid = %gossiped_tx.id()))] + #[allow(clippy::unwrap_in_result)] pub fn download_if_needed_and_verify( &mut self, gossiped_tx: Gossip, diff --git a/zebrad/tests/common/checkpoints.rs b/zebrad/tests/common/checkpoints.rs index 602525fd926..c1c0ae44716 100644 --- a/zebrad/tests/common/checkpoints.rs +++ b/zebrad/tests/common/checkpoints.rs @@ -136,7 +136,7 @@ pub async fn run(network: Network) -> Result<()> { ?zebra_rpc_address, "waiting for zebrad to open its RPC port...", ); - zebrad.expect_stdout_line_matches(&format!("Opened RPC endpoint at {zebra_rpc_address}"))?; + zebrad.expect_stdout_line_matches(format!("Opened RPC endpoint at {zebra_rpc_address}"))?; tracing::info!( ?network, diff --git a/zebrad/tests/common/get_block_template_rpcs/get_peer_info.rs b/zebrad/tests/common/get_block_template_rpcs/get_peer_info.rs index 4ca0bc797ad..dd30954948c 100644 --- a/zebrad/tests/common/get_block_template_rpcs/get_peer_info.rs +++ b/zebrad/tests/common/get_block_template_rpcs/get_peer_info.rs @@ -34,7 +34,7 @@ pub(crate) async fn run() -> Result<()> { let rpc_address = zebra_rpc_address.expect("getpeerinfo test must have RPC port"); // Wait until port is open. - zebrad.expect_stdout_line_matches(&format!("Opened RPC endpoint at {rpc_address}"))?; + zebrad.expect_stdout_line_matches(format!("Opened RPC endpoint at {rpc_address}"))?; tracing::info!(?rpc_address, "zebrad opened its RPC port",); diff --git a/zebrad/tests/common/get_block_template_rpcs/submit_block.rs b/zebrad/tests/common/get_block_template_rpcs/submit_block.rs index 28f48fb2c14..399efc8d99e 100644 --- a/zebrad/tests/common/get_block_template_rpcs/submit_block.rs +++ b/zebrad/tests/common/get_block_template_rpcs/submit_block.rs @@ -59,7 +59,7 @@ pub(crate) async fn run() -> Result<()> { ?rpc_address, "spawned isolated zebrad with shorter chain, waiting for zebrad to open its RPC port..." ); - zebrad.expect_stdout_line_matches(&format!("Opened RPC endpoint at {rpc_address}"))?; + zebrad.expect_stdout_line_matches(format!("Opened RPC endpoint at {rpc_address}"))?; tracing::info!(?rpc_address, "zebrad opened its RPC port",); diff --git a/zebrad/tests/common/lightwalletd/send_transaction_test.rs b/zebrad/tests/common/lightwalletd/send_transaction_test.rs index f9087771595..bee6cf78356 100644 --- a/zebrad/tests/common/lightwalletd/send_transaction_test.rs +++ b/zebrad/tests/common/lightwalletd/send_transaction_test.rs @@ -118,7 +118,7 @@ pub async fn run() -> Result<()> { ?zebra_rpc_address, "spawned isolated zebrad with shorter chain, waiting for zebrad to open its RPC port..." ); - zebrad.expect_stdout_line_matches(&format!("Opened RPC endpoint at {zebra_rpc_address}"))?; + zebrad.expect_stdout_line_matches(format!("Opened RPC endpoint at {zebra_rpc_address}"))?; tracing::info!( ?zebra_rpc_address, From 554a37d20a08f02312508bc6c4e18844ec44ff21 Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Mon, 9 Sep 2024 10:02:35 -0300 Subject: [PATCH 11/15] feat(rpc): Add a `stop` rpc method (#8839) * add a `stop` rpc method * add todo comment * add a ticket number to the TODO Co-authored-by: Marek --------- Co-authored-by: Marek Co-authored-by: Pili Guerra --- openapi.yaml | 85 +++++++++++++------ zebra-rpc/src/methods.rs | 25 ++++++ zebra-utils/src/bin/openapi-generator/main.rs | 1 + 3 files changed, 85 insertions(+), 26 deletions(-) diff --git a/openapi.yaml b/openapi.yaml index 10ac6bfdf61..e7793967118 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -28,7 +28,7 @@ paths: default: getinfo id: type: string - default: VuJXrxLSw8 + default: QWlDS9bxlK params: type: array items: {} @@ -61,7 +61,7 @@ paths: default: getblockchaininfo id: type: string - default: HDVqYXM9m6 + default: XSg3wvZykA params: type: array items: {} @@ -99,7 +99,7 @@ paths: default: getaddressbalance id: type: string - default: Xw5TDBKXGl + default: GEd1QJWprH params: type: array items: {} @@ -147,7 +147,7 @@ paths: default: sendrawtransaction id: type: string - default: QaJv2bXyZu + default: nhQi7D6Oru params: type: array items: {} @@ -196,7 +196,7 @@ paths: default: getblock id: type: string - default: k0DACJrgZs + default: qIEYMzgbJZ params: type: array items: {} @@ -239,7 +239,7 @@ paths: default: getbestblockhash id: type: string - default: rIFaLhZwHF + default: P9UBS8IXXU params: type: array items: {} @@ -272,7 +272,7 @@ paths: default: getbestblockheightandhash id: type: string - default: oxrhh1swvh + default: gQNhsomx7N params: type: array items: {} @@ -305,7 +305,7 @@ paths: default: getrawmempool id: type: string - default: E7oUD34jk2 + default: c2ScL31PtX params: type: array items: {} @@ -343,7 +343,7 @@ paths: default: z_gettreestate id: type: string - default: Hp22XK728i + default: JQ0mENKbdm params: type: array items: {} @@ -393,7 +393,7 @@ paths: default: z_getsubtreesbyindex id: type: string - default: Cs69hg68pl + default: bZUCv4t0f4 params: type: array items: {} @@ -432,7 +432,7 @@ paths: default: getrawtransaction id: type: string - default: iu395PEErc + default: I0FAejAi4r params: type: array items: {} @@ -480,7 +480,7 @@ paths: default: getaddresstxids id: type: string - default: z3lOKfsQdp + default: '3fMzDHOglf' params: type: array items: {} @@ -528,7 +528,7 @@ paths: default: getaddressutxos id: type: string - default: '7U4Q4dSxej' + default: LE2AR8Tr6X params: type: array items: {} @@ -554,6 +554,39 @@ paths: error: type: string default: Invalid parameters + /stop: + post: + tags: + - control + description: Stop the running zebrad process. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + method: + type: string + default: stop + id: + type: string + default: PbxxqB0ZpF + params: + type: array + items: {} + default: '[]' + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + result: + type: object + default: 'null' /getblockcount: post: tags: @@ -571,7 +604,7 @@ paths: default: getblockcount id: type: string - default: '8yw3EX7Cwi' + default: WO6BAIKSCg params: type: array items: {} @@ -609,7 +642,7 @@ paths: default: getblockhash id: type: string - default: ndDYksCl9E + default: vHpKNIQRLF params: type: array items: {} @@ -657,7 +690,7 @@ paths: default: getblocktemplate id: type: string - default: lJi2hfxty1 + default: L04jp5F2QW params: type: array items: {} @@ -695,7 +728,7 @@ paths: default: submitblock id: type: string - default: '9fEFOdTQle' + default: Izn7vhiMaA params: type: array items: {} @@ -728,7 +761,7 @@ paths: default: getmininginfo id: type: string - default: Dytpq4f3lF + default: SgyuBQbMik params: type: array items: {} @@ -761,7 +794,7 @@ paths: default: getnetworksolps id: type: string - default: yX3woRnOaN + default: FXg2iH3eaX params: type: array items: {} @@ -794,7 +827,7 @@ paths: default: getnetworkhashps id: type: string - default: AyAZMtbezv + default: '2PWjf8QqfI' params: type: array items: {} @@ -827,7 +860,7 @@ paths: default: getpeerinfo id: type: string - default: nNcrsu3ZAR + default: OE9s5wkP0w params: type: array items: {} @@ -865,7 +898,7 @@ paths: default: validateaddress id: type: string - default: LGyfO7zTjW + default: '6FS4iGA4Ht' params: type: array items: {} @@ -903,7 +936,7 @@ paths: default: z_validateaddress id: type: string - default: '2Q09a2Nh4N' + default: utp8tN61yU params: type: array items: {} @@ -941,7 +974,7 @@ paths: default: getblocksubsidy id: type: string - default: nv6lOWCRso + default: dgNZGo7lNa params: type: array items: {} @@ -984,7 +1017,7 @@ paths: default: getdifficulty id: type: string - default: '2O3A0PF1SS' + default: KEJv30D2MI params: type: array items: {} @@ -1022,7 +1055,7 @@ paths: default: z_listunifiedreceivers id: type: string - default: XYgGcDIx2X + default: lfBqvYghGm params: type: array items: {} diff --git a/zebra-rpc/src/methods.rs b/zebra-rpc/src/methods.rs index 471d542922c..cb894182c1f 100644 --- a/zebra-rpc/src/methods.rs +++ b/zebra-rpc/src/methods.rs @@ -301,6 +301,18 @@ pub trait Rpc { &self, address_strings: AddressStrings, ) -> BoxFuture>>; + + #[rpc(name = "stop")] + /// Stop the running zebrad process. + /// + /// # Notes + /// + /// Only works if the network of the running zebrad process is `Regtest`. + /// + /// zcashd reference: [`stop`](https://zcash.github.io/rpc/stop.html) + /// method: post + /// tags: control + fn stop(&self) -> Result<()>; } /// RPC method implementations. @@ -1344,6 +1356,19 @@ where } .boxed() } + + fn stop(&self) -> Result<()> { + if self.network.is_regtest() { + // TODO: Use graceful termination in `stop` RPC (#8850) + std::process::exit(0); + } else { + Err(Error { + code: ErrorCode::MethodNotFound, + message: "stop is only available on regtest networks".to_string(), + data: None, + }) + } + } } /// Returns the best chain tip height of `latest_chain_tip`, diff --git a/zebra-utils/src/bin/openapi-generator/main.rs b/zebra-utils/src/bin/openapi-generator/main.rs index fd0e9fe9b2b..15e5446d855 100644 --- a/zebra-utils/src/bin/openapi-generator/main.rs +++ b/zebra-utils/src/bin/openapi-generator/main.rs @@ -543,6 +543,7 @@ fn get_default_properties(method_name: &str) -> Result default_property(type_, items.clone(), GetInfo::default())?, + "stop" => default_property(type_, items.clone(), ())?, // transaction "sendrawtransaction" => { default_property(type_, items.clone(), SentTransactionHash::default())? From 082cdad1c1f00a622d21c83a84cb4bfa6797fe06 Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Mon, 9 Sep 2024 18:51:37 -0300 Subject: [PATCH 12/15] feat(rpc): Add a `generate` rpc method (#8849) * implement `generate` rpc method * update openapi --------- Co-authored-by: Pili Guerra --- openapi.yaml | 102 +++++++++++++----- .../src/methods/get_block_template_rpcs.rs | 77 ++++++++++++- 2 files changed, 150 insertions(+), 29 deletions(-) diff --git a/openapi.yaml b/openapi.yaml index e7793967118..abc70299814 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -28,7 +28,7 @@ paths: default: getinfo id: type: string - default: QWlDS9bxlK + default: dX2SRjFwfc params: type: array items: {} @@ -61,7 +61,7 @@ paths: default: getblockchaininfo id: type: string - default: XSg3wvZykA + default: LoRrjyRM4l params: type: array items: {} @@ -99,7 +99,7 @@ paths: default: getaddressbalance id: type: string - default: GEd1QJWprH + default: WWIvpPiJo0 params: type: array items: {} @@ -147,7 +147,7 @@ paths: default: sendrawtransaction id: type: string - default: nhQi7D6Oru + default: '5tVg2R9ZeI' params: type: array items: {} @@ -196,7 +196,7 @@ paths: default: getblock id: type: string - default: qIEYMzgbJZ + default: vZ5KPOdiue params: type: array items: {} @@ -239,7 +239,7 @@ paths: default: getbestblockhash id: type: string - default: P9UBS8IXXU + default: IifeYgN2ZK params: type: array items: {} @@ -272,7 +272,7 @@ paths: default: getbestblockheightandhash id: type: string - default: gQNhsomx7N + default: tNLKsWqtNW params: type: array items: {} @@ -305,7 +305,7 @@ paths: default: getrawmempool id: type: string - default: c2ScL31PtX + default: IZ6todle9t params: type: array items: {} @@ -343,7 +343,7 @@ paths: default: z_gettreestate id: type: string - default: JQ0mENKbdm + default: SSZAwyUO6t params: type: array items: {} @@ -393,7 +393,7 @@ paths: default: z_getsubtreesbyindex id: type: string - default: bZUCv4t0f4 + default: '3fJMQ0Hfxt' params: type: array items: {} @@ -432,7 +432,7 @@ paths: default: getrawtransaction id: type: string - default: I0FAejAi4r + default: RTdE1YnNxy params: type: array items: {} @@ -480,7 +480,7 @@ paths: default: getaddresstxids id: type: string - default: '3fMzDHOglf' + default: ifahwzVoYe params: type: array items: {} @@ -528,7 +528,7 @@ paths: default: getaddressutxos id: type: string - default: LE2AR8Tr6X + default: PcPdZ7aiKy params: type: array items: {} @@ -571,7 +571,7 @@ paths: default: stop id: type: string - default: PbxxqB0ZpF + default: rWlJLGe7VJ params: type: array items: {} @@ -604,7 +604,7 @@ paths: default: getblockcount id: type: string - default: WO6BAIKSCg + default: f4p3Cb4sDu params: type: array items: {} @@ -642,7 +642,7 @@ paths: default: getblockhash id: type: string - default: vHpKNIQRLF + default: '3QXvqbEWqb' params: type: array items: {} @@ -690,7 +690,7 @@ paths: default: getblocktemplate id: type: string - default: L04jp5F2QW + default: GXKjn81k0D params: type: array items: {} @@ -728,7 +728,7 @@ paths: default: submitblock id: type: string - default: Izn7vhiMaA + default: cwGy92Mwn9 params: type: array items: {} @@ -761,7 +761,7 @@ paths: default: getmininginfo id: type: string - default: SgyuBQbMik + default: '4ZFY9ljh5I' params: type: array items: {} @@ -794,7 +794,7 @@ paths: default: getnetworksolps id: type: string - default: FXg2iH3eaX + default: tJlKGzARjU params: type: array items: {} @@ -827,7 +827,7 @@ paths: default: getnetworkhashps id: type: string - default: '2PWjf8QqfI' + default: '7pUkOt26PB' params: type: array items: {} @@ -860,7 +860,7 @@ paths: default: getpeerinfo id: type: string - default: OE9s5wkP0w + default: JjnSrPKeyS params: type: array items: {} @@ -898,7 +898,7 @@ paths: default: validateaddress id: type: string - default: '6FS4iGA4Ht' + default: pxZQt6VQ9U params: type: array items: {} @@ -936,7 +936,7 @@ paths: default: z_validateaddress id: type: string - default: utp8tN61yU + default: x2R2oRhdZE params: type: array items: {} @@ -974,7 +974,7 @@ paths: default: getblocksubsidy id: type: string - default: dgNZGo7lNa + default: vkhYJS3FH8 params: type: array items: {} @@ -1017,7 +1017,7 @@ paths: default: getdifficulty id: type: string - default: KEJv30D2MI + default: bC6q9c3xYO params: type: array items: {} @@ -1055,7 +1055,7 @@ paths: default: z_listunifiedreceivers id: type: string - default: lfBqvYghGm + default: EQvPXkcJC2 params: type: array items: {} @@ -1071,3 +1071,51 @@ paths: result: type: object default: '{"orchard":"orchard address if any","sapling":"sapling address if any","p2pkh":"p2pkh address if any","p2sh":"p2sh address if any"}' + /generate: + post: + tags: + - generating + description: |- + Mine blocks immediately. Returns the block hashes of the generated blocks. + + **Request body `params` arguments:** + + - `num_blocks` - Number of blocks to be generated. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + method: + type: string + default: generate + id: + type: string + default: w41FKROii3 + params: + type: array + items: {} + default: '[1]' + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + result: + type: object + default: '{}' + '400': + description: Bad request + content: + application/json: + schema: + type: object + properties: + error: + type: string + default: Invalid parameters diff --git a/zebra-rpc/src/methods/get_block_template_rpcs.rs b/zebra-rpc/src/methods/get_block_template_rpcs.rs index c8c83e9315a..826f8d3e930 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs.rs @@ -19,7 +19,7 @@ use zebra_chain::{ Network, NetworkKind, NetworkUpgrade, POW_AVERAGING_WINDOW, }, primitives, - serialization::ZcashDeserializeInto, + serialization::{ZcashDeserializeInto, ZcashSerialize}, transparent::{ self, EXTRA_ZEBRA_COINBASE_DATA, MAX_COINBASE_DATA_LEN, MAX_COINBASE_HEIGHT_DATA_LEN, }, @@ -47,7 +47,9 @@ use crate::methods::{ // TODO: move the types/* modules directly under get_block_template_rpcs, // and combine any modules with the same names. types::{ - get_block_template::GetBlockTemplate, + get_block_template::{ + proposal::TimeSource, proposal_block_from_template, GetBlockTemplate, + }, get_mining_info, long_poll::LongPollInput, peer_info::PeerInfo, @@ -283,6 +285,22 @@ pub trait GetBlockTemplateRpc { &self, address: String, ) -> BoxFuture>; + + #[rpc(name = "generate")] + /// Mine blocks immediately. Returns the block hashes of the generated blocks. + /// + /// # Parameters + /// + /// - `num_blocks`: (numeric, required, example=1) Number of blocks to be generated. + /// + /// # Notes + /// + /// Only works if the network of the running zebrad process is `Regtest`. + /// + /// zcashd reference: [`generate`](https://zcash.github.io/rpc/generate.html) + /// method: post + /// tags: generating + fn generate(&self, num_blocks: u32) -> BoxFuture>>; } /// RPC method implementations. @@ -1357,6 +1375,61 @@ where } .boxed() } + + fn generate(&self, num_blocks: u32) -> BoxFuture>> { + let rpc: GetBlockTemplateRpcImpl< + Mempool, + State, + Tip, + BlockVerifierRouter, + SyncStatus, + AddressBook, + > = self.clone(); + let network = self.network.clone(); + + async move { + if !network.is_regtest() { + return Err(Error { + code: ErrorCode::ServerError(0), + message: "generate is only supported on regtest".to_string(), + data: None, + }); + } + + let mut block_hashes = Vec::new(); + for _ in 0..num_blocks { + let block_template = rpc.get_block_template(None).await.map_server_error()?; + + let get_block_template::Response::TemplateMode(block_template) = block_template + else { + return Err(Error { + code: ErrorCode::ServerError(0), + message: "error generating block template".to_string(), + data: None, + }); + }; + + let proposal_block = proposal_block_from_template( + &block_template, + TimeSource::CurTime, + NetworkUpgrade::current(&network, Height(block_template.height)), + ) + .map_server_error()?; + let hex_proposal_block = + HexData(proposal_block.zcash_serialize_to_vec().map_server_error()?); + + let _submit = rpc + .submit_block(hex_proposal_block, None) + .await + .map_server_error()?; + + block_hashes.push(GetBlockHash(proposal_block.hash())); + } + + Ok(block_hashes) + } + .boxed() + } } // Put support functions in a submodule, to keep this file small. From 3f94303bb24e23f5ee4c3aff39c65d6d3d813243 Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Wed, 18 Sep 2024 08:05:25 -0300 Subject: [PATCH 13/15] feat(rpc): Add more fields to `getmininginfo` call (#8860) * add additional fields to getmininginfo * update openapi spec * fix zebra-state standalone build * make sure fields are not present when tip is 0 --- openapi.yaml | 58 +++++++++---------- zebra-chain/src/chain_tip/mock.rs | 2 +- .../src/methods/get_block_template_rpcs.rs | 30 ++++++++++ .../types/get_mining_info.rs | 23 +++++++- .../snapshots/get_mining_info@mainnet_10.snap | 2 + .../snapshots/get_mining_info@testnet_10.snap | 2 + zebra-state/src/request.rs | 7 +++ zebra-state/src/response.rs | 6 +- zebra-state/src/service.rs | 43 ++++++++++++++ 9 files changed, 141 insertions(+), 32 deletions(-) diff --git a/openapi.yaml b/openapi.yaml index abc70299814..58a754c9731 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -28,7 +28,7 @@ paths: default: getinfo id: type: string - default: dX2SRjFwfc + default: uf2E54tQkk params: type: array items: {} @@ -61,7 +61,7 @@ paths: default: getblockchaininfo id: type: string - default: LoRrjyRM4l + default: Sbre3vivr8 params: type: array items: {} @@ -99,7 +99,7 @@ paths: default: getaddressbalance id: type: string - default: WWIvpPiJo0 + default: f5qarOBgzK params: type: array items: {} @@ -147,7 +147,7 @@ paths: default: sendrawtransaction id: type: string - default: '5tVg2R9ZeI' + default: IlNHvAcSMS params: type: array items: {} @@ -196,7 +196,7 @@ paths: default: getblock id: type: string - default: vZ5KPOdiue + default: s9678BM3Lc params: type: array items: {} @@ -239,7 +239,7 @@ paths: default: getbestblockhash id: type: string - default: IifeYgN2ZK + default: FGQPJY8Tp8 params: type: array items: {} @@ -272,7 +272,7 @@ paths: default: getbestblockheightandhash id: type: string - default: tNLKsWqtNW + default: c2MfkL7xP9 params: type: array items: {} @@ -305,7 +305,7 @@ paths: default: getrawmempool id: type: string - default: IZ6todle9t + default: BugnNFhJpA params: type: array items: {} @@ -343,7 +343,7 @@ paths: default: z_gettreestate id: type: string - default: SSZAwyUO6t + default: fCUQvR1BVa params: type: array items: {} @@ -393,7 +393,7 @@ paths: default: z_getsubtreesbyindex id: type: string - default: '3fJMQ0Hfxt' + default: TtPnptV6EU params: type: array items: {} @@ -432,7 +432,7 @@ paths: default: getrawtransaction id: type: string - default: RTdE1YnNxy + default: QqYeOGSzje params: type: array items: {} @@ -480,7 +480,7 @@ paths: default: getaddresstxids id: type: string - default: ifahwzVoYe + default: AsWWVyqp8x params: type: array items: {} @@ -528,7 +528,7 @@ paths: default: getaddressutxos id: type: string - default: PcPdZ7aiKy + default: Qscn5dUFgD params: type: array items: {} @@ -571,7 +571,7 @@ paths: default: stop id: type: string - default: rWlJLGe7VJ + default: WuIaPXV5fO params: type: array items: {} @@ -604,7 +604,7 @@ paths: default: getblockcount id: type: string - default: f4p3Cb4sDu + default: '5F9M7Wp0oI' params: type: array items: {} @@ -642,7 +642,7 @@ paths: default: getblockhash id: type: string - default: '3QXvqbEWqb' + default: f7hdgVjctr params: type: array items: {} @@ -690,7 +690,7 @@ paths: default: getblocktemplate id: type: string - default: GXKjn81k0D + default: pq0uXn3YGs params: type: array items: {} @@ -728,7 +728,7 @@ paths: default: submitblock id: type: string - default: cwGy92Mwn9 + default: bs4v4JmVw3 params: type: array items: {} @@ -761,7 +761,7 @@ paths: default: getmininginfo id: type: string - default: '4ZFY9ljh5I' + default: pp5xV6v3pm params: type: array items: {} @@ -776,7 +776,7 @@ paths: properties: result: type: object - default: '{"networksolps":0,"networkhashps":0,"chain":"","testnet":false}' + default: '{"blocks":0,"networksolps":0,"networkhashps":0,"chain":"","testnet":false}' /getnetworksolps: post: tags: @@ -794,7 +794,7 @@ paths: default: getnetworksolps id: type: string - default: tJlKGzARjU + default: '7bU98TeCV6' params: type: array items: {} @@ -827,7 +827,7 @@ paths: default: getnetworkhashps id: type: string - default: '7pUkOt26PB' + default: fskOJeXqjo params: type: array items: {} @@ -860,7 +860,7 @@ paths: default: getpeerinfo id: type: string - default: JjnSrPKeyS + default: jPV8ufjDdt params: type: array items: {} @@ -898,7 +898,7 @@ paths: default: validateaddress id: type: string - default: pxZQt6VQ9U + default: xOyxICseV9 params: type: array items: {} @@ -936,7 +936,7 @@ paths: default: z_validateaddress id: type: string - default: x2R2oRhdZE + default: xa6PoC4uN6 params: type: array items: {} @@ -974,7 +974,7 @@ paths: default: getblocksubsidy id: type: string - default: vkhYJS3FH8 + default: vYEVtnVK9o params: type: array items: {} @@ -1017,7 +1017,7 @@ paths: default: getdifficulty id: type: string - default: bC6q9c3xYO + default: tVzSTZu2sD params: type: array items: {} @@ -1055,7 +1055,7 @@ paths: default: z_listunifiedreceivers id: type: string - default: EQvPXkcJC2 + default: le2NmJBmPt params: type: array items: {} @@ -1093,7 +1093,7 @@ paths: default: generate id: type: string - default: w41FKROii3 + default: vVVOWxHqlN params: type: array items: {} diff --git a/zebra-chain/src/chain_tip/mock.rs b/zebra-chain/src/chain_tip/mock.rs index 46ca5e89e5e..f1fc8fb6e27 100644 --- a/zebra-chain/src/chain_tip/mock.rs +++ b/zebra-chain/src/chain_tip/mock.rs @@ -106,7 +106,7 @@ impl ChainTip for MockChainTip { } fn best_tip_mined_transaction_ids(&self) -> Arc<[transaction::Hash]> { - unreachable!("Method not used in tests"); + Arc::new([]) } fn estimate_distance_to_network_chain_tip( diff --git a/zebra-rpc/src/methods/get_block_template_rpcs.rs b/zebra-rpc/src/methods/get_block_template_rpcs.rs index 826f8d3e930..2d50552cfec 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs.rs @@ -1012,9 +1012,39 @@ where fn get_mining_info(&self) -> BoxFuture> { let network = self.network.clone(); + let mut state = self.state.clone(); + + let chain_tip = self.latest_chain_tip.clone(); + let tip_height = chain_tip.best_tip_height().unwrap_or(Height(0)).0; + + let mut current_block_tx = None; + if tip_height > 0 { + let mined_tx_ids = chain_tip.best_tip_mined_transaction_ids(); + current_block_tx = + (!mined_tx_ids.is_empty()).then(|| mined_tx_ids.len().saturating_sub(1)); + } + let solution_rate_fut = self.get_network_sol_ps(None, None); async move { + // Get the current block size. + let mut current_block_size = None; + if tip_height > 0 { + let request = zebra_state::ReadRequest::TipBlockSize; + let response: zebra_state::ReadResponse = state + .ready() + .and_then(|service| service.call(request)) + .await + .map_server_error()?; + current_block_size = match response { + zebra_state::ReadResponse::TipBlockSize(Some(block_size)) => Some(block_size), + _ => None, + }; + } + Ok(get_mining_info::Response::new( + tip_height, + current_block_size, + current_block_tx, network, solution_rate_fut.await?, )) diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_mining_info.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_mining_info.rs index a14d4a081e7..21627d509db 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_mining_info.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_mining_info.rs @@ -5,6 +5,18 @@ use zebra_chain::parameters::Network; /// Response to a `getmininginfo` RPC request. #[derive(Debug, Default, PartialEq, Eq, serde::Serialize)] pub struct Response { + /// The current tip height. + #[serde(rename = "blocks")] + tip_height: u32, + + /// The size of the last mined block if any. + #[serde(rename = "currentblocksize", skip_serializing_if = "Option::is_none")] + current_block_size: Option, + + /// The number of transactions in the last mined block if any. + #[serde(rename = "currentblocktx", skip_serializing_if = "Option::is_none")] + current_block_tx: Option, + /// The estimated network solution rate in Sol/s. networksolps: u64, @@ -20,8 +32,17 @@ pub struct Response { impl Response { /// Creates a new `getmininginfo` response - pub fn new(network: Network, networksolps: u64) -> Self { + pub fn new( + tip_height: u32, + current_block_size: Option, + current_block_tx: Option, + network: Network, + networksolps: u64, + ) -> Self { Self { + tip_height, + current_block_size, + current_block_tx, networksolps, networkhashps: networksolps, chain: network.bip70_network_name(), diff --git a/zebra-rpc/src/methods/tests/snapshot/snapshots/get_mining_info@mainnet_10.snap b/zebra-rpc/src/methods/tests/snapshot/snapshots/get_mining_info@mainnet_10.snap index 67ffde393c4..de309513443 100644 --- a/zebra-rpc/src/methods/tests/snapshot/snapshots/get_mining_info@mainnet_10.snap +++ b/zebra-rpc/src/methods/tests/snapshot/snapshots/get_mining_info@mainnet_10.snap @@ -3,6 +3,8 @@ source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs expression: get_mining_info --- { + "blocks": 1687104, + "currentblocksize": 1617, "networksolps": 2, "networkhashps": 2, "chain": "main", diff --git a/zebra-rpc/src/methods/tests/snapshot/snapshots/get_mining_info@testnet_10.snap b/zebra-rpc/src/methods/tests/snapshot/snapshots/get_mining_info@testnet_10.snap index fc728a8540f..2051e6913ce 100644 --- a/zebra-rpc/src/methods/tests/snapshot/snapshots/get_mining_info@testnet_10.snap +++ b/zebra-rpc/src/methods/tests/snapshot/snapshots/get_mining_info@testnet_10.snap @@ -3,6 +3,8 @@ source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs expression: get_mining_info --- { + "blocks": 1842420, + "currentblocksize": 1618, "networksolps": 0, "networkhashps": 0, "chain": "test", diff --git a/zebra-state/src/request.rs b/zebra-state/src/request.rs index 28740a336bb..1863c56b2ed 100644 --- a/zebra-state/src/request.rs +++ b/zebra-state/src/request.rs @@ -1063,6 +1063,11 @@ pub enum ReadRequest { /// Returns [`ReadResponse::ValidBlockProposal`] when successful, or an error if /// the block fails contextual validation. CheckBlockProposalValidity(SemanticallyVerifiedBlock), + + #[cfg(feature = "getblocktemplate-rpcs")] + /// Returns [`ReadResponse::TipBlockSize(usize)`](ReadResponse::TipBlockSize) + /// with the current best chain tip block size in bytes. + TipBlockSize, } impl ReadRequest { @@ -1098,6 +1103,8 @@ impl ReadRequest { ReadRequest::SolutionRate { .. } => "solution_rate", #[cfg(feature = "getblocktemplate-rpcs")] ReadRequest::CheckBlockProposalValidity(_) => "check_block_proposal_validity", + #[cfg(feature = "getblocktemplate-rpcs")] + ReadRequest::TipBlockSize => "tip_block_size", } } diff --git a/zebra-state/src/response.rs b/zebra-state/src/response.rs index 22e610838de..77c252b0c75 100644 --- a/zebra-state/src/response.rs +++ b/zebra-state/src/response.rs @@ -229,6 +229,10 @@ pub enum ReadResponse { #[cfg(feature = "getblocktemplate-rpcs")] /// Response to [`ReadRequest::CheckBlockProposalValidity`] ValidBlockProposal, + + #[cfg(feature = "getblocktemplate-rpcs")] + /// Response to [`ReadRequest::TipBlockSize`] + TipBlockSize(Option), } /// A structure with the information needed from the state to build a `getblocktemplate` RPC response. @@ -315,7 +319,7 @@ impl TryFrom for Response { ReadResponse::ValidBlockProposal => Ok(Response::ValidBlockProposal), #[cfg(feature = "getblocktemplate-rpcs")] - ReadResponse::ChainInfo(_) | ReadResponse::SolutionRate(_) => { + ReadResponse::ChainInfo(_) | ReadResponse::SolutionRate(_) | ReadResponse::TipBlockSize(_) => { Err("there is no corresponding Response for this ReadResponse") } } diff --git a/zebra-state/src/service.rs b/zebra-state/src/service.rs index 2116ab10470..4f970be89d4 100644 --- a/zebra-state/src/service.rs +++ b/zebra-state/src/service.rs @@ -39,6 +39,9 @@ use zebra_chain::{ subtree::NoteCommitmentSubtreeIndex, }; +#[cfg(feature = "getblocktemplate-rpcs")] +use zebra_chain::{block::Height, serialization::ZcashSerialize}; + use crate::{ constants::{ MAX_FIND_BLOCK_HASHES_RESULTS, MAX_FIND_BLOCK_HEADERS_RESULTS_FOR_ZEBRA, @@ -1905,6 +1908,46 @@ impl Service for ReadStateService { }) .wait_for_panics() } + + #[cfg(feature = "getblocktemplate-rpcs")] + ReadRequest::TipBlockSize => { + let state = self.clone(); + + tokio::task::spawn_blocking(move || { + span.in_scope(move || { + // Get the best chain tip height. + let tip_height = state + .non_finalized_state_receiver + .with_watch_data(|non_finalized_state| { + read::tip_height(non_finalized_state.best_chain(), &state.db) + }) + .unwrap_or(Height(0)); + + // Get the block at the best chain tip height. + let block = state.non_finalized_state_receiver.with_watch_data( + |non_finalized_state| { + read::block( + non_finalized_state.best_chain(), + &state.db, + tip_height.into(), + ) + }, + ); + + // The work is done in the future. + timer.finish(module_path!(), line!(), "ReadRequest::TipBlockSize"); + + // Respond with the length of the obtained block if any. + match block { + Some(b) => Ok(ReadResponse::TipBlockSize(Some( + b.zcash_serialize_to_vec()?.len(), + ))), + None => Ok(ReadResponse::TipBlockSize(None)), + } + }) + }) + .wait_for_panics() + } } } } From 60d09a4e62e8fa7cdf282f2a85c109c81fa905bf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Sep 2024 17:39:38 +0000 Subject: [PATCH 14/15] build(deps): bump tj-actions/changed-files in the devops group (#8874) Bumps the devops group with 1 update: [tj-actions/changed-files](https://github.com/tj-actions/changed-files). Updates `tj-actions/changed-files` from 45.0.1 to 45.0.2 - [Release notes](https://github.com/tj-actions/changed-files/releases) - [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md) - [Commits](https://github.com/tj-actions/changed-files/compare/v45.0.1...v45.0.2) --- updated-dependencies: - dependency-name: tj-actions/changed-files dependency-type: direct:production update-type: version-update:semver-patch dependency-group: devops ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-lint.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-lint.yml b/.github/workflows/ci-lint.yml index 87b60204746..a2ea13523b8 100644 --- a/.github/workflows/ci-lint.yml +++ b/.github/workflows/ci-lint.yml @@ -44,7 +44,7 @@ jobs: - name: Rust files id: changed-files-rust - uses: tj-actions/changed-files@v45.0.1 + uses: tj-actions/changed-files@v45.0.2 with: files: | **/*.rs @@ -56,7 +56,7 @@ jobs: - name: Workflow files id: changed-files-workflows - uses: tj-actions/changed-files@v45.0.1 + uses: tj-actions/changed-files@v45.0.2 with: files: | .github/workflows/*.yml From c5d8eb5f83d2189dbae6917e05200f78694a6215 Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Wed, 18 Sep 2024 17:14:41 -0300 Subject: [PATCH 15/15] fix(rpc): modify shutdown used in `stop()` (#8863) * modify shutdown used in `stop()` * use conditional compilation * add note * fix conditional compilation --- Cargo.lock | 19 +++++++++++++++++++ zebra-rpc/Cargo.toml | 2 ++ zebra-rpc/src/methods.rs | 26 ++++++++++++++++++++------ 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ae84f840965..22f5d505038 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -692,6 +692,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chacha20" version = "0.9.1" @@ -2650,6 +2656,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.6.0", + "cfg-if 1.0.0", + "cfg_aliases", + "libc", +] + [[package]] name = "nom" version = "7.1.3" @@ -6154,6 +6172,7 @@ dependencies = [ "jsonrpc-core", "jsonrpc-derive", "jsonrpc-http-server", + "nix", "proptest", "prost", "rand 0.8.5", diff --git a/zebra-rpc/Cargo.toml b/zebra-rpc/Cargo.toml index e45df94b000..babae9123f1 100644 --- a/zebra-rpc/Cargo.toml +++ b/zebra-rpc/Cargo.toml @@ -87,6 +87,8 @@ tracing = "0.1.39" hex = { version = "0.4.3", features = ["serde"] } serde = { version = "1.0.204", features = ["serde_derive"] } +# For the `stop` RPC method. +nix = { version = "0.29.0", features = ["signal"] } zcash_primitives = { workspace = true, features = ["transparent-inputs"] } diff --git a/zebra-rpc/src/methods.rs b/zebra-rpc/src/methods.rs index cb894182c1f..8becc5bb79c 100644 --- a/zebra-rpc/src/methods.rs +++ b/zebra-rpc/src/methods.rs @@ -302,17 +302,18 @@ pub trait Rpc { address_strings: AddressStrings, ) -> BoxFuture>>; - #[rpc(name = "stop")] /// Stop the running zebrad process. /// /// # Notes /// - /// Only works if the network of the running zebrad process is `Regtest`. + /// - Works for non windows targets only. + /// - Works only if the network of the running zebrad process is `Regtest`. /// /// zcashd reference: [`stop`](https://zcash.github.io/rpc/stop.html) /// method: post /// tags: control - fn stop(&self) -> Result<()>; + #[rpc(name = "stop")] + fn stop(&self) -> Result; } /// RPC method implementations. @@ -1357,10 +1358,17 @@ where .boxed() } - fn stop(&self) -> Result<()> { + fn stop(&self) -> Result { + #[cfg(not(target_os = "windows"))] if self.network.is_regtest() { - // TODO: Use graceful termination in `stop` RPC (#8850) - std::process::exit(0); + match nix::sys::signal::raise(nix::sys::signal::SIGINT) { + Ok(_) => Ok("Zebra server stopping".to_string()), + Err(error) => Err(Error { + code: ErrorCode::InternalError, + message: format!("Failed to shut down: {}", error), + data: None, + }), + } } else { Err(Error { code: ErrorCode::MethodNotFound, @@ -1368,6 +1376,12 @@ where data: None, }) } + #[cfg(target_os = "windows")] + Err(Error { + code: ErrorCode::MethodNotFound, + message: "stop is not available in windows targets".to_string(), + data: None, + }) } }