diff --git a/zebra-rpc/src/config.rs b/zebra-rpc/src/config.rs index c08b7da9..a9be9488 100644 --- a/zebra-rpc/src/config.rs +++ b/zebra-rpc/src/config.rs @@ -55,6 +55,8 @@ pub struct Config { pub debug_force_finished_sync: bool, } +// This impl isn't derivable because it depends on features. +#[allow(clippy::derivable_impls)] impl Default for Config { fn default() -> Self { Self { @@ -62,8 +64,13 @@ impl Default for Config { listen_addr: None, // Use a single thread, so we can detect RPC port conflicts. + #[cfg(not(feature = "getblocktemplate-rpcs"))] parallel_cpu_threads: 1, + // Use multiple threads, because we pause requests during getblocktemplate long polling + #[cfg(feature = "getblocktemplate-rpcs")] + parallel_cpu_threads: 0, + // Debug options are always off by default. debug_force_finished_sync: false, } diff --git a/zebra-rpc/src/methods/get_block_template_rpcs.rs b/zebra-rpc/src/methods/get_block_template_rpcs.rs index f3f3b4d4..bfe98f55 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs.rs @@ -31,10 +31,17 @@ use zebra_state::{ReadRequest, ReadResponse}; use crate::methods::{ best_chain_tip_height, - get_block_template_rpcs::types::{ - default_roots::DefaultRoots, get_block_template::GetBlockTemplate, - get_block_template_opts::GetBlockTemplateRequestMode, hex_data::HexData, submit_block, - transaction::TransactionTemplate, + get_block_template_rpcs::{ + constants::{ + DEFAULT_SOLUTION_RATE_WINDOW_SIZE, GET_BLOCK_TEMPLATE_CAPABILITIES_FIELD, + GET_BLOCK_TEMPLATE_MUTABLE_FIELD, GET_BLOCK_TEMPLATE_NONCE_RANGE_FIELD, + MAX_ESTIMATED_DISTANCE_TO_NETWORK_CHAIN_TIP, NOT_SYNCED_ERROR_CODE, + }, + types::{ + default_roots::DefaultRoots, get_block_template::GetBlockTemplate, + get_block_template_opts::GetBlockTemplateRequestMode, hex_data::HexData, + long_poll::LongPollInput, submit_block, transaction::TransactionTemplate, + }, }, GetBlockHash, MISSING_BLOCK_ERROR_CODE, }; @@ -44,25 +51,6 @@ pub mod constants; pub mod types; pub mod zip317; -/// The max estimated distance to the chain tip for the getblocktemplate method. -/// -/// Allows the same clock skew as the Zcash network, which is 100 blocks, based on the standard rule: -/// > A full validator MUST NOT accept blocks with nTime more than two hours in the future -/// > according to its clock. This is not strictly a consensus rule because it is nondeterministic, -/// > and clock time varies between nodes. -const MAX_ESTIMATED_DISTANCE_TO_NETWORK_CHAIN_TIP: i32 = 100; - -/// The default window size specifying how many blocks to check when estimating the chain's solution rate. -/// -/// Based on default value in zcashd. -const DEFAULT_SOLUTION_RATE_WINDOW_SIZE: usize = 120; - -/// The RPC error code used by `zcashd` for when it's still downloading initial blocks. -/// -/// `s-nomp` mining pool expects error code `-10` when the node is not synced: -/// -pub const NOT_SYNCED_ERROR_CODE: ErrorCode = ErrorCode::ServerError(-10); - /// getblocktemplate RPC method signatures. #[rpc(server)] pub trait GetBlockTemplateRpc { @@ -355,14 +343,6 @@ where data: None, }) } - - if options.longpollid.is_some() { - return Err(Error { - code: ErrorCode::InvalidParams, - message: "long polling is currently unsupported by Zebra".to_string(), - data: None, - }) - } } let miner_address = miner_address.ok_or_else(|| Error { @@ -375,7 +355,7 @@ where // The tip estimate may not be the same as the one coming from the state // but this is ok for an estimate - let (estimated_distance_to_chain_tip, estimated_tip_height) = latest_chain_tip + let (estimated_distance_to_chain_tip, local_tip_height) = latest_chain_tip .estimate_distance_to_network_chain_tip(network) .ok_or_else(|| Error { code: ErrorCode::ServerError(0), @@ -386,7 +366,7 @@ where if !sync_status.is_close_to_tip() || estimated_distance_to_chain_tip > MAX_ESTIMATED_DISTANCE_TO_NETWORK_CHAIN_TIP { tracing::info!( estimated_distance_to_chain_tip, - ?estimated_tip_height, + ?local_tip_height, "Zebra has not synced to the chain tip" ); @@ -441,13 +421,23 @@ where &auth_data_root, ); + // TODO: use the entire mempool content via a watch channel, + // rather than just the randomly selected transactions + let long_poll_id = LongPollInput::new( + chain_info.tip_height, + chain_info.tip_hash, + chain_info.max_time, + mempool_txs.iter().map(|tx| tx.transaction.id), + ).into(); + // Convert into TransactionTemplates let mempool_txs = mempool_txs.iter().map(Into::into).collect(); - let mutable: Vec = constants::GET_BLOCK_TEMPLATE_MUTABLE_FIELD.iter().map(ToString::to_string).collect(); + let capabilities: Vec = GET_BLOCK_TEMPLATE_CAPABILITIES_FIELD.iter().map(ToString::to_string).collect(); + let mutable: Vec = GET_BLOCK_TEMPLATE_MUTABLE_FIELD.iter().map(ToString::to_string).collect(); Ok(GetBlockTemplate { - capabilities: Vec::new(), + capabilities, version: ZCASH_BLOCK_VERSION, @@ -466,6 +456,8 @@ where coinbase_txn: TransactionTemplate::from_coinbase(&coinbase_tx, miner_fee), + long_poll_id, + target: format!( "{}", chain_info.expected_difficulty @@ -477,7 +469,7 @@ where mutable, - nonce_range: constants::GET_BLOCK_TEMPLATE_NONCE_RANGE_FIELD.to_string(), + nonce_range: GET_BLOCK_TEMPLATE_NONCE_RANGE_FIELD.to_string(), sigop_limit: MAX_BLOCK_SIGOPS, diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/constants.rs b/zebra-rpc/src/methods/get_block_template_rpcs/constants.rs index 2db3ed05..03d7296e 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/constants.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/constants.rs @@ -1,7 +1,37 @@ //! Constant values used in mining rpcs methods. -/// A range of valid nonces that goes from `u32::MIN` to `u32::MAX` as a string. +use jsonrpc_core::ErrorCode; + +/// A range of valid block template nonces, that goes from `u32::MIN` to `u32::MAX` as a string. pub const GET_BLOCK_TEMPLATE_NONCE_RANGE_FIELD: &str = "00000000ffffffff"; -/// A hardcoded list of fields that the miner can change from the block. -pub const GET_BLOCK_TEMPLATE_MUTABLE_FIELD: &[&str] = &["time", "transactions", "prevblock"]; +/// A hardcoded list of fields that the miner can change from the block template. +pub const GET_BLOCK_TEMPLATE_MUTABLE_FIELD: &[&str] = &[ + // Standard mutations, copied from zcashd + "time", + "transactions", + "prevblock", +]; + +/// A hardcoded list of Zebra's getblocktemplate RPC capabilities. +/// Currently empty. +pub const GET_BLOCK_TEMPLATE_CAPABILITIES_FIELD: &[&str] = &[]; + +/// The max estimated distance to the chain tip for the getblocktemplate method. +/// +/// Allows the same clock skew as the Zcash network, which is 100 blocks, based on the standard rule: +/// > A full validator MUST NOT accept blocks with nTime more than two hours in the future +/// > according to its clock. This is not strictly a consensus rule because it is nondeterministic, +/// > and clock time varies between nodes. +pub const MAX_ESTIMATED_DISTANCE_TO_NETWORK_CHAIN_TIP: i32 = 100; + +/// The RPC error code used by `zcashd` for when it's still downloading initial blocks. +/// +/// `s-nomp` mining pool expects error code `-10` when the node is not synced: +/// +pub const NOT_SYNCED_ERROR_CODE: ErrorCode = ErrorCode::ServerError(-10); + +/// The default window size specifying how many blocks to check when estimating the chain's solution rate. +/// +/// Based on default value in zcashd. +pub const DEFAULT_SOLUTION_RATE_WINDOW_SIZE: usize = 120; diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types.rs index 4d20ad10..802d9911 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types.rs @@ -5,5 +5,6 @@ pub mod get_block_template; pub mod get_block_template_opts; pub mod get_mining_info; pub mod hex_data; +pub mod long_poll; pub mod submit_block; pub mod transaction; diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template.rs index b3a0576d..8e68a0a3 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template.rs @@ -4,7 +4,7 @@ use zebra_chain::{amount, block::ChainHistoryBlockTxAuthCommitmentHash}; use crate::methods::{ get_block_template_rpcs::types::{ - default_roots::DefaultRoots, transaction::TransactionTemplate, + default_roots::DefaultRoots, long_poll::LongPollId, transaction::TransactionTemplate, }, GetBlockHash, }; @@ -67,6 +67,10 @@ pub struct GetBlockTemplate { #[serde(rename = "coinbasetxn")] pub coinbase_txn: TransactionTemplate, + /// An ID that represents the chain tip and mempool contents for this template. + #[serde(rename = "longpollid")] + pub long_poll_id: LongPollId, + /// The expected difficulty for the new block displayed in expanded form. // TODO: use ExpandedDifficulty type. pub target: String, diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template_opts.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template_opts.rs index 2718a081..90af8e34 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template_opts.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template_opts.rs @@ -1,6 +1,6 @@ //! Parameter types for the `getblocktemplate` RPC. -use super::hex_data::HexData; +use super::{hex_data::HexData, long_poll::LongPollId}; /// Defines whether the RPC method should generate a block template or attempt to validate a block proposal. /// `Proposal` mode is currently unsupported and will return an error. @@ -82,10 +82,8 @@ pub struct JsonParameters { #[serde(default)] pub capabilities: Vec, - /// An id to wait for, in zcashd this is the tip hash and an internal counter. + /// An ID that delays the RPC response until the template changes. /// - /// If provided, the RPC response is delayed until the mempool or chain tip block changes. - /// - /// Currently unsupported and ignored by Zebra. - pub longpollid: Option, + /// In Zebra, the ID represents the chain tip, max time, and mempool contents. + pub longpollid: Option, } diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/long_poll.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/long_poll.rs new file mode 100644 index 00000000..e10d5240 --- /dev/null +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/long_poll.rs @@ -0,0 +1,279 @@ +//! Long polling support for the `getblocktemplate` RPC. +//! +//! These implementation details are private, and should not be relied upon by miners. +//! They are also different from the `zcashd` implementation of long polling. + +use std::{str::FromStr, sync::Arc}; + +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +use zebra_chain::{ + block::{self, Height}, + transaction::{self, UnminedTxId}, +}; +use zebra_node_services::BoxError; + +/// The length of a serialized [`LongPollId`] string. +/// +/// This is an internal Zebra implementation detail, which does not need to match `zcashd`. +pub const LONG_POLL_ID_LENGTH: usize = 46; + +/// The inputs to the long polling check. +/// +/// If these inputs change, Zebra should return a response to any open long polls. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct LongPollInput { + // Fields that invalidate old work: + // + /// The tip height used to generate the template containing this long poll ID. + /// + /// If the tip block height changes, a new template must be provided. + /// Old work is no longer valid. + /// + /// The height is technically redundant, but it helps with debugging. + /// It also reduces the probability of a missed tip change. + pub tip_height: Height, + + /// The tip hash used to generate the template containing this long poll ID. + /// + /// If the tip block changes, a new template must be provided. + /// Old work is no longer valid. + pub tip_hash: block::Hash, + + /// The max time in the same template as this long poll ID. + /// + /// If the max time is reached, a new template must be provided. + /// Old work is no longer valid. + /// + /// Ideally, a new template should be provided at least one target block interval before + /// the max time. This avoids wasted work. + pub max_time: DateTime, + + // Fields that allow old work: + // + /// The effecting hashes of the transactions in the mempool, + /// when the template containing this long poll ID was generated. + /// We ignore changes to authorizing data. + /// + /// This might be different from the transactions in the template, due to ZIP-317. + /// + /// If the mempool transactions change, a new template might be provided. + /// Old work is still valid. + pub mempool_transaction_mined_ids: Arc<[transaction::Hash]>, +} + +impl LongPollInput { + /// Returns a new [`LongPollInput`], based on the supplied fields. + pub fn new( + tip_height: Height, + tip_hash: block::Hash, + max_time: DateTime, + mempool_tx_ids: impl IntoIterator, + ) -> Self { + let mempool_transaction_mined_ids = + mempool_tx_ids.into_iter().map(|id| id.mined_id()).collect(); + + LongPollInput { + tip_height, + tip_hash, + max_time, + mempool_transaction_mined_ids, + } + } +} + +/// The encoded long poll ID, generated from the [`LongPollInput`]. +/// +/// `zcashd` IDs are currently 69 hex/decimal digits long. +/// Since Zebra's IDs are only 46 hex/decimal digits, mining pools should be able to handle them. +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(try_from = "String", into = "String")] +pub struct LongPollId { + // Fields that invalidate old work: + // + /// The tip height used to generate the template containing this long poll ID. + /// + /// If the tip block height changes, a new template must be provided. + /// Old work is no longer valid. + /// + /// The height is technically redundant, but it helps with debugging. + /// It also reduces the probability of a missed tip change. + pub tip_height: u32, + + /// A checksum of the tip hash used to generate the template containing this long poll ID. + /// + /// If the tip block changes, a new template must be provided. + /// Old work is no longer valid. + /// This checksum is not cryptographically secure. + /// + /// It's ok to do a probabilistic check here, + /// so we choose a 1 in 2^32 chance of missing a block change. + pub tip_hash_checksum: u32, + + /// The max time in the same template as this long poll ID. + /// + /// If the max time is reached, a new template must be provided. + /// Old work is no longer valid. + /// + /// Ideally, a new template should be provided at least one target block interval before + /// the max time. This avoids wasted work. + /// + /// Zcash times are limited to 32 bits by the consensus rules. + pub max_timestamp: u32, + + // Fields that allow old work: + // + /// The number of transactions in the mempool when the template containing this long poll ID + /// was generated. This might be different from the number of transactions in the template, + /// due to ZIP-317. + /// + /// If the number of mempool transactions changes, a new template might be provided. + /// Old work is still valid. + /// + /// The number of transactions is limited by the mempool DoS limit. + /// + /// Using the number of transactions makes mempool checksum attacks much harder. + /// It also helps with debugging, and reduces the probability of a missed mempool change. + pub mempool_transaction_count: u32, + + /// A checksum of the effecting hashes of the transactions in the mempool, + /// when the template containing this long poll ID was generated. + /// We ignore changes to authorizing data. + /// + /// This might be different from the transactions in the template, due to ZIP-317. + /// + /// If the content of the mempool changes, a new template might be provided. + /// Old work is still valid. + /// + /// This checksum is not cryptographically secure. + /// + /// It's ok to do a probabilistic check here, + /// so we choose a 1 in 2^32 chance of missing a transaction change. + /// + /// # Security + /// + /// Attackers could use dust transactions to keep the checksum at the same value. + /// But this would likely change the number of transactions in the mempool. + /// + /// If an attacker could also keep the number of transactions constant, + /// a new template will be generated when the tip hash changes, or the max time is reached. + pub mempool_transaction_content_checksum: u32, +} + +impl From for LongPollId { + /// Lossy conversion from LongPollInput to LongPollId. + fn from(input: LongPollInput) -> Self { + let mut tip_hash_checksum = 0; + update_checksum(&mut tip_hash_checksum, input.tip_hash.0); + + let mut mempool_transaction_content_checksum: u32 = 0; + for tx_mined_id in input.mempool_transaction_mined_ids.iter() { + update_checksum(&mut mempool_transaction_content_checksum, tx_mined_id.0); + } + + Self { + tip_height: input.tip_height.0, + + tip_hash_checksum, + + // It's ok to do wrapping conversions here, + // because long polling checks are probabilistic. + max_timestamp: input.max_time.timestamp() as u32, + + mempool_transaction_count: input.mempool_transaction_mined_ids.len() as u32, + + mempool_transaction_content_checksum, + } + } +} + +/// Update `checksum` from `item`, so changes in `item` are likely to also change `checksum`. +/// +/// This checksum is not cryptographically secure. +fn update_checksum(checksum: &mut u32, item: [u8; 32]) { + for chunk in item.chunks(4) { + let chunk = chunk.try_into().expect("chunk is u32 size"); + + // The endianness of this conversion doesn't matter, + // so we make it efficient on the most common platforms. + let chunk = u32::from_le_bytes(chunk); + + // It's ok to use xor here, because long polling checks are probabilistic, + // and the height, time, and transaction count fields will detect most changes. + // + // Without those fields, miners could game the xor-ed block hash, + // and hide block changes from other miners, gaining an advantage. + // But this would reduce their profit under proof of work, + // because the first valid block hash a miner generates will pay + // a significant block subsidy. + *checksum ^= chunk; + } +} + +impl ToString for LongPollId { + /// Exact conversion from LongPollId to a string. + fn to_string(&self) -> String { + let LongPollId { + tip_height, + tip_hash_checksum, + max_timestamp, + mempool_transaction_count, + mempool_transaction_content_checksum, + } = self; + + // We can't do this using `serde`, because it names each field, + // but we want a single string containing all the fields. + format!( + // Height as decimal, padded with zeroes to the width of Height::MAX + // Checksums as hex, padded with zeroes to the width of u32::MAX + // Timestamp as decimal, padded with zeroes to the width of u32::MAX + // Transaction Count as decimal, padded with zeroes to the width of u32::MAX + "{tip_height:010}\ + {tip_hash_checksum:08x}\ + {max_timestamp:010}\ + {mempool_transaction_count:010}\ + {mempool_transaction_content_checksum:08x}" + ) + } +} + +impl FromStr for LongPollId { + type Err = BoxError; + + /// Exact conversion from a string to LongPollId. + fn from_str(long_poll_id: &str) -> Result { + if long_poll_id.len() != LONG_POLL_ID_LENGTH { + return Err(format!( + "incorrect long poll id length, must be {LONG_POLL_ID_LENGTH} for Zebra" + ) + .into()); + } + + Ok(Self { + tip_height: long_poll_id[0..10].parse()?, + tip_hash_checksum: u32::from_str_radix(&long_poll_id[10..18], 16)?, + max_timestamp: long_poll_id[18..28].parse()?, + mempool_transaction_count: long_poll_id[28..38].parse()?, + mempool_transaction_content_checksum: u32::from_str_radix( + &long_poll_id[38..LONG_POLL_ID_LENGTH], + 16, + )?, + }) + } +} + +// Wrappers for serde conversion +impl From for String { + fn from(id: LongPollId) -> Self { + id.to_string() + } +} + +impl TryFrom for LongPollId { + type Error = BoxError; + + fn try_from(s: String) -> Result { + s.parse() + } +} diff --git a/zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_template@mainnet_10.snap b/zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_template@mainnet_10.snap index 1984ee99..7d6e5f22 100644 --- a/zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_template@mainnet_10.snap +++ b/zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_template@mainnet_10.snap @@ -25,6 +25,7 @@ expression: block_template "sigops": 0, "required": true }, + "longpollid": "00016871043eab7f731654008728000000000000000000", "target": "0000000000000000000000000000000000000000000000000000000000000001", "mintime": 1654008606, "mutable": [ diff --git a/zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_template@testnet_10.snap b/zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_template@testnet_10.snap index 34cd7eb2..e6284e9c 100644 --- a/zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_template@testnet_10.snap +++ b/zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_template@testnet_10.snap @@ -25,6 +25,7 @@ expression: block_template "sigops": 0, "required": true }, + "longpollid": "00018424203eab7f731654008728000000000000000000", "target": "0000000000000000000000000000000000000000000000000000000000000001", "mintime": 1654008606, "mutable": [ diff --git a/zebra-rpc/src/methods/tests/vectors.rs b/zebra-rpc/src/methods/tests/vectors.rs index 4136d9db..df5c6ef2 100644 --- a/zebra-rpc/src/methods/tests/vectors.rs +++ b/zebra-rpc/src/methods/tests/vectors.rs @@ -881,12 +881,6 @@ async fn rpc_getblocktemplate() { use chrono::{TimeZone, Utc}; - use crate::methods::{ - get_block_template_rpcs::constants::{ - GET_BLOCK_TEMPLATE_MUTABLE_FIELD, GET_BLOCK_TEMPLATE_NONCE_RANGE_FIELD, - }, - tests::utils::fake_history_tree, - }; use zebra_chain::{ amount::{Amount, NonNegative}, block::{Hash, MAX_BLOCK_BYTES, ZCASH_BLOCK_VERSION}, @@ -894,9 +888,19 @@ async fn rpc_getblocktemplate() { work::difficulty::{CompactDifficulty, ExpandedDifficulty, U256}, }; use zebra_consensus::MAX_BLOCK_SIGOPS; - use zebra_state::{GetBlockTemplateChainInfo, ReadRequest, ReadResponse}; + use crate::methods::{ + get_block_template_rpcs::{ + constants::{ + GET_BLOCK_TEMPLATE_CAPABILITIES_FIELD, GET_BLOCK_TEMPLATE_MUTABLE_FIELD, + GET_BLOCK_TEMPLATE_NONCE_RANGE_FIELD, + }, + types::long_poll::LONG_POLL_ID_LENGTH, + }, + tests::utils::fake_history_tree, + }; + let _init_guard = zebra_test::init(); let mut mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests(); @@ -973,7 +977,10 @@ async fn rpc_getblocktemplate() { }) .expect("unexpected error in getblocktemplate RPC call"); - assert_eq!(get_block_template.capabilities, Vec::::new()); + assert_eq!( + get_block_template.capabilities, + GET_BLOCK_TEMPLATE_CAPABILITIES_FIELD.to_vec() + ); assert_eq!(get_block_template.version, ZCASH_BLOCK_VERSION); assert!(get_block_template.transactions.is_empty()); assert_eq!( @@ -1044,6 +1051,7 @@ async fn rpc_getblocktemplate() { get_block_template_sync_error.code, ErrorCode::ServerError(-10) ); + let get_block_template_sync_error = get_block_template_rpc .get_block_template(Some(get_block_template_rpcs::types::get_block_template_opts::JsonParameters { mode: get_block_template_rpcs::types::get_block_template_opts::GetBlockTemplateRequestMode::Proposal, @@ -1066,17 +1074,27 @@ async fn rpc_getblocktemplate() { assert_eq!(get_block_template_sync_error.code, ErrorCode::InvalidParams); + // The long poll id is valid, so it returns a state error instead let get_block_template_sync_error = get_block_template_rpc .get_block_template(Some( get_block_template_rpcs::types::get_block_template_opts::JsonParameters { - longpollid: Some("".to_string()), + // This must parse as a LongPollId. + // It must be the correct length and have hex/decimal digits. + longpollid: Some( + "0".repeat(LONG_POLL_ID_LENGTH) + .parse() + .expect("unexpected invalid LongPollId"), + ), ..Default::default() }, )) .await - .expect_err("needs an error when using unsupported option"); + .expect_err("needs an error when the state is empty"); - assert_eq!(get_block_template_sync_error.code, ErrorCode::InvalidParams); + assert_eq!( + get_block_template_sync_error.code, + ErrorCode::ServerError(-10) + ); } #[cfg(feature = "getblocktemplate-rpcs")] diff --git a/zebrad/tests/acceptance.rs b/zebrad/tests/acceptance.rs index 2afc1874..c75b8292 100644 --- a/zebrad/tests/acceptance.rs +++ b/zebrad/tests/acceptance.rs @@ -575,6 +575,7 @@ fn config_tests() -> Result<()> { } /// Test that `zebrad` runs the start command with no args +#[tracing::instrument] fn app_no_args() -> Result<()> { let _init_guard = zebra_test::init(); @@ -654,6 +655,8 @@ fn valid_generated_config(command: &str, expect_stdout_line_contains: &str) -> R } /// Check if the config produced by current zebrad is stored. +#[tracing::instrument] +#[allow(clippy::print_stdout)] fn last_config_is_stored() -> Result<()> { let _init_guard = zebra_test::init(); @@ -709,14 +712,30 @@ fn last_config_is_stored() -> Result<()> { } } + println!( + "\n\ + Here is the missing config file: \n\ + \n\ + {processed_generated_content}\n" + ); + Err(eyre!( - "latest zebrad config is not being tested for compatibility.\n\ - Run: \n\ + "latest zebrad config is not being tested for compatibility. \n\ + \n\ + Take the missing config file logged above, \n\ + and commit it to Zebra's git repository as:\n\ + zebrad/tests/common/configs/{}.toml \n\ + \n\ + Or run: \n\ cargo build {}--bin zebrad && \n\ zebrad generate | \n\ sed \"s/cache_dir = '.*'/cache_dir = 'cache_dir'/\" > \n\ - zebrad/tests/common/configs/{}.toml \n\ - and commit the latest config to Zebra's git repository", + zebrad/tests/common/configs/{}.toml", + if cfg!(feature = "getblocktemplate-rpcs") { + GET_BLOCK_TEMPLATE_CONFIG_PREFIX + } else { + "" + }, if cfg!(feature = "getblocktemplate-rpcs") { "--features=getblocktemplate-rpcs " } else { @@ -732,6 +751,7 @@ fn last_config_is_stored() -> Result<()> { /// Checks that Zebra prints an informative message when it cannot parse the /// config file. +#[tracing::instrument] fn invalid_generated_config() -> Result<()> { let _init_guard = zebra_test::init(); @@ -804,6 +824,7 @@ fn invalid_generated_config() -> Result<()> { } /// Test all versions of `zebrad.toml` we have stored can be parsed by the latest `zebrad`. +#[tracing::instrument] fn stored_configs_works() -> Result<()> { let old_configs_dir = configs_dir(); diff --git a/zebrad/tests/common/configs/getblocktemplate-v1.0.0-rc.3.toml b/zebrad/tests/common/configs/getblocktemplate-v1.0.0-rc.3.toml new file mode 100644 index 00000000..379c491b --- /dev/null +++ b/zebrad/tests/common/configs/getblocktemplate-v1.0.0-rc.3.toml @@ -0,0 +1,72 @@ +# Default configuration for zebrad. +# +# This file can be used as a skeleton for custom configs. +# +# Unspecified fields use default values. Optional fields are Some(field) if the +# field is present and None if it is absent. +# +# This file is generated as an example using zebrad's current defaults. +# You should set only the config options you want to keep, and delete the rest. +# Only a subset of fields are present in the skeleton, since optional values +# whose default is None are omitted. +# +# The config format (including a complete list of sections and fields) is +# documented here: +# https://doc.zebra.zfnd.org/zebrad/config/struct.ZebradConfig.html +# +# zebrad attempts to load configs in the following order: +# +# 1. The -c flag on the command line, e.g., `zebrad -c myconfig.toml start`; +# 2. The file `zebrad.toml` in the users's preference directory (platform-dependent); +# 3. The default config. + +[consensus] +checkpoint_sync = true +debug_skip_parameter_preload = false + +[mempool] +eviction_memory_time = '1h' +tx_cost_limit = 80000000 + +[metrics] + +[mining] + +[network] +crawl_new_peer_interval = '1m 1s' +initial_mainnet_peers = [ + 'dnsseed.z.cash:8233', + 'dnsseed.str4d.xyz:8233', + 'mainnet.seeder.zfnd.org:8233', + 'mainnet.is.yolo.money:8233', +] +initial_testnet_peers = [ + 'dnsseed.testnet.z.cash:18233', + 'testnet.seeder.zfnd.org:18233', + 'testnet.is.yolo.money:18233', +] +listen_addr = '0.0.0.0:8233' +network = 'Mainnet' +peerset_initial_target_size = 25 + +[rpc] +debug_force_finished_sync = false +parallel_cpu_threads = 0 + +[state] +cache_dir = 'cache_dir' +delete_old_database = true +ephemeral = false + +[sync] +checkpoint_verify_concurrency_limit = 1000 +download_concurrency_limit = 50 +full_verify_concurrency_limit = 20 +parallel_cpu_threads = 0 + +[tracing] +buffer_limit = 128000 +force_use_color = false +use_color = true +use_journald = false +