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:
parent
2041fda7bb
commit
a6e6eb5051
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -31,10 +31,17 @@ use zebra_state::{ReadRequest, ReadResponse};
|
|||
|
||||
use crate::methods::{
|
||||
best_chain_tip_height,
|
||||
get_block_template_rpcs::types::{
|
||||
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, submit_block,
|
||||
transaction::TransactionTemplate,
|
||||
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,
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>,
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -25,6 +25,7 @@ expression: block_template
|
|||
"sigops": 0,
|
||||
"required": true
|
||||
},
|
||||
"longpollid": "00016871043eab7f731654008728000000000000000000",
|
||||
"target": "0000000000000000000000000000000000000000000000000000000000000001",
|
||||
"mintime": 1654008606,
|
||||
"mutable": [
|
||||
|
|
|
@ -25,6 +25,7 @@ expression: block_template
|
|||
"sigops": 0,
|
||||
"required": true
|
||||
},
|
||||
"longpollid": "00018424203eab7f731654008728000000000000000000",
|
||||
"target": "0000000000000000000000000000000000000000000000000000000000000001",
|
||||
"mintime": 1654008606,
|
||||
"mutable": [
|
||||
|
|
|
@ -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")]
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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
|
||||
|
Loading…
Reference in New Issue