change(rpc): Provide and parse a long poll ID, but don't use it yet (#5796)

* Declare support for long polling in the getblocktemplate RPC

* Add long polling input and ID structs

* Split out an update_checksum() function

* Implement LongPollId conversion to and from a string

* Use the LongPollId type in the RPC

* Add a longpollid field to the getblocktemplate parameters and responses, but don't use it yet

* Use multiple RPC threads with the getblocktemplate feature, to enable efficient long polling

* Update RPC snapshots

* Remove the "longpoll" capability, it's for miners, not nodes

* Use the long poll length constant in tests

* Update snapshots

* Remove the "long polling is not supported" error

* Fix minor compilation issues after the merge/rebase

* Expand long poll id comments

* Rename estimated height to local height, because that's what it actually is

* Add an invalid params test and fix the long poll id test

* Add modified config for config_tests

* Instrument all the config sub-tests

* Show the missing config file when the test fails

* Fix the generated config file

* Allow a clippy lint in tests

* Explain conversion from bytes to u32

* Remove a duplicate test case
This commit is contained in:
teor 2022-12-09 11:41:46 +10:00 committed by GitHub
parent 2041fda7bb
commit a6e6eb5051
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 485 additions and 61 deletions

View File

@ -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,
}

View File

@ -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:
/// <https://github.com/s-nomp/node-stratum-pool/blob/d86ae73f8ff968d9355bb61aac05e0ebef36ccb5/lib/pool.js#L142>
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<String> = constants::GET_BLOCK_TEMPLATE_MUTABLE_FIELD.iter().map(ToString::to_string).collect();
let capabilities: Vec<String> = GET_BLOCK_TEMPLATE_CAPABILITIES_FIELD.iter().map(ToString::to_string).collect();
let mutable: Vec<String> = 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,

View File

@ -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:
/// <https://github.com/s-nomp/node-stratum-pool/blob/d86ae73f8ff968d9355bb61aac05e0ebef36ccb5/lib/pool.js#L142>
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;

View File

@ -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;

View File

@ -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<amount::NegativeOrZero>,
/// 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,

View File

@ -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<GetBlockTemplateCapability>,
/// 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<String>,
/// In Zebra, the ID represents the chain tip, max time, and mempool contents.
pub longpollid: Option<LongPollId>,
}

View File

@ -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<Utc>,
// 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<Utc>,
mempool_tx_ids: impl IntoIterator<Item = UnminedTxId>,
) -> 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<LongPollInput> 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<Self, Self::Err> {
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<LongPollId> for String {
fn from(id: LongPollId) -> Self {
id.to_string()
}
}
impl TryFrom<String> for LongPollId {
type Error = BoxError;
fn try_from(s: String) -> Result<Self, Self::Error> {
s.parse()
}
}

View File

@ -25,6 +25,7 @@ expression: block_template
"sigops": 0,
"required": true
},
"longpollid": "00016871043eab7f731654008728000000000000000000",
"target": "0000000000000000000000000000000000000000000000000000000000000001",
"mintime": 1654008606,
"mutable": [

View File

@ -25,6 +25,7 @@ expression: block_template
"sigops": 0,
"required": true
},
"longpollid": "00018424203eab7f731654008728000000000000000000",
"target": "0000000000000000000000000000000000000000000000000000000000000001",
"mintime": 1654008606,
"mutable": [

View File

@ -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::<String>::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")]

View File

@ -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/{}<next-release-tag>.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/{}<next-release-tag>.toml \n\
and commit the latest config to Zebra's git repository",
zebrad/tests/common/configs/{}<next-release-tag>.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();

View File

@ -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