test(rpc): Create a script that submits block proposals to zcashd (#5944)
* Revert "Update code that we're going to delete in the next PR anyway"
This reverts commit 1fed70da9e
.
* Initial zcash-test-block-template script without block proposal construction
* Try creating block data using jq and printf
* Move proposal_block_from_template() to zebra-rpc and remove eyre dependency
* Implement FromStr for DateTime32 and Duration32
* Basic block-template-to-proposal command without time source handling
* Move block proposal code into its own module
* Use time source in block-template-to-proposal
* Make block-template-to-proposal require the getblocktemplate-rpcs feature
* Use block-template-to-proposal in the test script zcash-rpc-block-template-to-proposal
* Apply new hex formatting to commitments and nonces in block proposal tests
* Re-add missing imports from rebase
* Re-add missing conversions from rebase
* Update to new method name after rebase
* Derive a Default impl
* Restore a comment that was accidentally deleted during the rebase
* Avoid a clippy::unwrap-in-result
* Temporarily fix the code for a disabled test
* Fix tool build with Docker caches
* Make clippy happy with the temporary fix
* Give a pass/fail status for each proposal response
* Accept zcashd block templates in block-template-to-proposal
* Fix pretty printing
* Zebra expects a longpollid, so give it a dummy value
* Add "required" fields which Zebra requires
* Use curtime as the dummy maxtime in zcashd templates
* Support large block proposals by reading proposal data from a file
* Test all valid time modes for each proposal
* Allow the user to set the time command
* Put debug logs into their own files
* Exit with an error status when any proposal is invalid
* Log the date and time to make it easier to match errors to node logs
This commit is contained in:
parent
256b1c0008
commit
6a06cbf3ad
|
@ -5633,6 +5633,7 @@ dependencies = [
|
|||
"tracing-subscriber 0.3.16",
|
||||
"zebra-chain",
|
||||
"zebra-node-services",
|
||||
"zebra-rpc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
//! DateTime types with specific serialization invariants.
|
||||
|
||||
use std::{fmt, num::TryFromIntError};
|
||||
use std::{
|
||||
fmt,
|
||||
num::{ParseIntError, TryFromIntError},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||
use chrono::{TimeZone, Utc};
|
||||
|
||||
use super::{SerializationError, ZcashDeserialize, ZcashSerialize};
|
||||
use crate::serialization::{SerializationError, ZcashDeserialize, ZcashSerialize};
|
||||
|
||||
/// A date and time, represented by a 32-bit number of seconds since the UNIX epoch.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
|
||||
|
@ -347,6 +351,26 @@ impl TryFrom<&std::time::Duration> for Duration32 {
|
|||
}
|
||||
}
|
||||
|
||||
impl FromStr for DateTime32 {
|
||||
type Err = ParseIntError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(DateTime32 {
|
||||
timestamp: s.parse()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Duration32 {
|
||||
type Err = ParseIntError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Duration32 {
|
||||
seconds: s.parse()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ZcashSerialize for DateTime32 {
|
||||
fn zcash_serialize<W: std::io::Write>(&self, mut writer: W) -> Result<(), std::io::Error> {
|
||||
writer.write_u32::<LittleEndian>(self.timestamp)
|
||||
|
|
|
@ -30,8 +30,8 @@ use crate::methods::{
|
|||
pub mod parameters;
|
||||
pub mod proposal;
|
||||
|
||||
pub use parameters::*;
|
||||
pub use proposal::*;
|
||||
pub use parameters::{GetBlockTemplateCapability, GetBlockTemplateRequestMode, JsonParameters};
|
||||
pub use proposal::{proposal_block_from_template, ProposalRejectReason, ProposalResponse};
|
||||
|
||||
/// A serialized `getblocktemplate` RPC response in template mode.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
|
@ -286,7 +286,7 @@ impl GetBlockTemplate {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(untagged)]
|
||||
/// A `getblocktemplate` RPC response.
|
||||
pub enum Response {
|
||||
|
|
|
@ -1,11 +1,27 @@
|
|||
//! getblocktemplate proposal mode implementation.
|
||||
//!
|
||||
//! `ProposalResponse` is the output of the `getblocktemplate` RPC method in 'proposal' mode.
|
||||
|
||||
use super::{GetBlockTemplate, Response};
|
||||
use std::{num::ParseIntError, str::FromStr, sync::Arc};
|
||||
|
||||
use zebra_chain::{
|
||||
block::{self, Block, Height},
|
||||
serialization::{DateTime32, SerializationError, ZcashDeserializeInto},
|
||||
work::equihash::Solution,
|
||||
};
|
||||
|
||||
use crate::methods::{
|
||||
get_block_template_rpcs::types::{
|
||||
default_roots::DefaultRoots,
|
||||
get_block_template::{GetBlockTemplate, Response},
|
||||
},
|
||||
GetBlockHash,
|
||||
};
|
||||
|
||||
/// Error response to a `getblocktemplate` RPC request in proposal mode.
|
||||
///
|
||||
/// See <https://en.bitcoin.it/wiki/BIP_0022#Appendix:_Example_Rejection_Reasons>
|
||||
#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum ProposalRejectReason {
|
||||
/// Block proposal rejected as invalid.
|
||||
|
@ -15,7 +31,7 @@ pub enum ProposalRejectReason {
|
|||
/// Response to a `getblocktemplate` RPC request in proposal mode.
|
||||
///
|
||||
/// See <https://en.bitcoin.it/wiki/BIP_0023#Block_Proposal>
|
||||
#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(untagged, rename_all = "kebab-case")]
|
||||
pub enum ProposalResponse {
|
||||
/// Block proposal was rejected as invalid, returns `reject-reason` and server `capabilities`.
|
||||
|
@ -57,3 +73,140 @@ impl From<GetBlockTemplate> for Response {
|
|||
Self::TemplateMode(Box::new(template))
|
||||
}
|
||||
}
|
||||
|
||||
/// The source of the time in the block proposal header.
|
||||
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
|
||||
pub enum TimeSource {
|
||||
/// The `curtime` field in the template.
|
||||
/// This is the default time source.
|
||||
#[default]
|
||||
CurTime,
|
||||
|
||||
/// The `mintime` field in the template.
|
||||
MinTime,
|
||||
|
||||
/// The `maxtime` field in the template.
|
||||
MaxTime,
|
||||
|
||||
/// The supplied time, clamped within the template's `[mintime, maxtime]`.
|
||||
Clamped(DateTime32),
|
||||
|
||||
/// The current local clock time, clamped within the template's `[mintime, maxtime]`.
|
||||
ClampedNow,
|
||||
|
||||
/// The raw supplied time, ignoring the `mintime` and `maxtime` in the template.
|
||||
///
|
||||
/// Warning: this can create an invalid block proposal.
|
||||
Raw(DateTime32),
|
||||
|
||||
/// The raw current local time, ignoring the `mintime` and `maxtime` in the template.
|
||||
///
|
||||
/// Warning: this can create an invalid block proposal.
|
||||
RawNow,
|
||||
}
|
||||
|
||||
impl TimeSource {
|
||||
/// Returns the time from `template` using this time source.
|
||||
pub fn time_from_template(&self, template: &GetBlockTemplate) -> DateTime32 {
|
||||
use TimeSource::*;
|
||||
|
||||
match self {
|
||||
CurTime => template.cur_time,
|
||||
MinTime => template.min_time,
|
||||
MaxTime => template.max_time,
|
||||
Clamped(time) => (*time).clamp(template.min_time, template.max_time),
|
||||
ClampedNow => DateTime32::now().clamp(template.min_time, template.max_time),
|
||||
Raw(time) => *time,
|
||||
RawNow => DateTime32::now(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if this time source uses `max_time` in any way, including clamping.
|
||||
pub fn uses_max_time(&self) -> bool {
|
||||
use TimeSource::*;
|
||||
|
||||
match self {
|
||||
CurTime | MinTime => false,
|
||||
MaxTime | Clamped(_) | ClampedNow => true,
|
||||
Raw(_) | RawNow => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for TimeSource {
|
||||
type Err = ParseIntError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
use TimeSource::*;
|
||||
|
||||
match s.to_lowercase().as_str() {
|
||||
"curtime" => Ok(CurTime),
|
||||
"mintime" => Ok(MinTime),
|
||||
"maxtime" => Ok(MaxTime),
|
||||
"clampednow" => Ok(ClampedNow),
|
||||
"rawnow" => Ok(RawNow),
|
||||
s => match s.strip_prefix("raw") {
|
||||
// "raw"u32
|
||||
Some(raw_value) => Ok(Raw(raw_value.parse()?)),
|
||||
// "clamped"u32 or just u32
|
||||
// this is the default if the argument is just a number
|
||||
None => Ok(Clamped(s.strip_prefix("clamped").unwrap_or(s).parse()?)),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a block proposal generated from a [`GetBlockTemplate`] RPC response.
|
||||
///
|
||||
/// If `time_source` is not supplied, uses the current time from the template.
|
||||
pub fn proposal_block_from_template(
|
||||
template: GetBlockTemplate,
|
||||
time_source: impl Into<Option<TimeSource>>,
|
||||
) -> Result<Block, SerializationError> {
|
||||
let GetBlockTemplate {
|
||||
version,
|
||||
height,
|
||||
previous_block_hash: GetBlockHash(previous_block_hash),
|
||||
default_roots:
|
||||
DefaultRoots {
|
||||
merkle_root,
|
||||
block_commitments_hash,
|
||||
..
|
||||
},
|
||||
bits: difficulty_threshold,
|
||||
ref coinbase_txn,
|
||||
transactions: ref tx_templates,
|
||||
..
|
||||
} = template;
|
||||
|
||||
if Height(height) > Height::MAX {
|
||||
Err(SerializationError::Parse(
|
||||
"height field must be lower than Height::MAX",
|
||||
))?;
|
||||
};
|
||||
|
||||
let time = time_source
|
||||
.into()
|
||||
.unwrap_or_default()
|
||||
.time_from_template(&template);
|
||||
|
||||
let mut transactions = vec![coinbase_txn.data.as_ref().zcash_deserialize_into()?];
|
||||
|
||||
for tx_template in tx_templates {
|
||||
transactions.push(tx_template.data.as_ref().zcash_deserialize_into()?);
|
||||
}
|
||||
|
||||
Ok(Block {
|
||||
header: Arc::new(block::Header {
|
||||
version,
|
||||
previous_block_hash,
|
||||
merkle_root,
|
||||
commitment_bytes: block_commitments_hash.bytes_in_serialized_order().into(),
|
||||
time: time.into(),
|
||||
difficulty_threshold,
|
||||
nonce: [0; 32].into(),
|
||||
solution: Solution::for_proposal(),
|
||||
}),
|
||||
transactions,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -4,9 +4,30 @@ authors = ["Zcash Foundation <zebra@zfnd.org>"]
|
|||
license = "MIT OR Apache-2.0"
|
||||
version = "1.0.0-beta.19"
|
||||
edition = "2021"
|
||||
|
||||
# Prevent accidental publication of this utility crate.
|
||||
publish = false
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[[bin]]
|
||||
name = "block-template-to-proposal"
|
||||
# this setting is required for Zebra's Docker build caches
|
||||
path = "src/bin/block-template-to-proposal/main.rs"
|
||||
required-features = ["getblocktemplate-rpcs"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
# Production features that activate extra dependencies, or extra features in dependencies
|
||||
|
||||
# Experimental mining RPC support
|
||||
getblocktemplate-rpcs = [
|
||||
"zebra-rpc/getblocktemplate-rpcs",
|
||||
"zebra-node-services/getblocktemplate-rpcs",
|
||||
"zebra-chain/getblocktemplate-rpcs",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
color-eyre = "0.6.2"
|
||||
# This is a transitive dependency via color-eyre.
|
||||
|
@ -21,3 +42,6 @@ tracing-subscriber = "0.3.16"
|
|||
|
||||
zebra-node-services = { path = "../zebra-node-services" }
|
||||
zebra-chain = { path = "../zebra-chain" }
|
||||
|
||||
# Experimental feature getblocktemplate-rpcs
|
||||
zebra-rpc = { path = "../zebra-rpc", optional = true }
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
//! block-template-to-proposal arguments
|
||||
//!
|
||||
//! For usage please refer to the program help: `block-template-to-proposal --help`
|
||||
|
||||
use structopt::StructOpt;
|
||||
|
||||
use zebra_rpc::methods::get_block_template_rpcs::get_block_template::proposal::TimeSource;
|
||||
|
||||
/// block-template-to-proposal arguments
|
||||
#[derive(Clone, Debug, Eq, PartialEq, StructOpt)]
|
||||
pub struct Args {
|
||||
/// The source of the time in the block proposal header.
|
||||
/// Format: "curtime", "mintime", "maxtime", ["clamped"]u32, "raw"u32
|
||||
/// Clamped times are clamped to the template's [`mintime`, `maxtime`].
|
||||
/// Raw times are used unmodified: this can produce invalid proposals.
|
||||
#[structopt(default_value = "CurTime", short, long)]
|
||||
pub time_source: TimeSource,
|
||||
|
||||
/// The JSON block template.
|
||||
/// If this argument is not supplied, the template is read from standard input.
|
||||
///
|
||||
/// The template and proposal structures are printed to stderr.
|
||||
#[structopt(last = true)]
|
||||
pub template: Option<String>,
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
//! Transforms a JSON block template into a hex-encoded block proposal.
|
||||
//!
|
||||
//! Prints the parsed template and parsed proposal structures to stderr.
|
||||
//!
|
||||
//! For usage please refer to the program help: `block-template-to-proposal --help`
|
||||
|
||||
use std::io::Read;
|
||||
|
||||
use color_eyre::eyre::Result;
|
||||
use serde_json::Value;
|
||||
use structopt::StructOpt;
|
||||
|
||||
use zebra_chain::serialization::{DateTime32, ZcashSerialize};
|
||||
use zebra_rpc::methods::get_block_template_rpcs::{
|
||||
get_block_template::proposal_block_from_template,
|
||||
types::{get_block_template::GetBlockTemplate, long_poll::LONG_POLL_ID_LENGTH},
|
||||
};
|
||||
use zebra_utils::init_tracing;
|
||||
|
||||
mod args;
|
||||
|
||||
/// The minimum number of characters in a valid `getblocktemplate JSON response.
|
||||
///
|
||||
/// The fields we use take up around ~800 bytes.
|
||||
const MIN_TEMPLATE_BYTES: usize = 500;
|
||||
|
||||
/// Process entry point for `block-template-to-proposal`
|
||||
#[allow(clippy::print_stdout, clippy::print_stderr)]
|
||||
fn main() -> Result<()> {
|
||||
// initialise
|
||||
init_tracing();
|
||||
color_eyre::install()?;
|
||||
|
||||
// get arguments from command-line or stdin
|
||||
let args = args::Args::from_args();
|
||||
|
||||
let time_source = args.time_source;
|
||||
|
||||
// Get template from command-line or standard input
|
||||
let template = args.template.unwrap_or_else(|| {
|
||||
let mut template = String::new();
|
||||
let bytes_read = std::io::stdin().read_to_string(&mut template).expect("missing JSON block template: must be supplied on command-line or standard input");
|
||||
|
||||
if bytes_read < MIN_TEMPLATE_BYTES {
|
||||
panic!("JSON block template is too small: expected at least {MIN_TEMPLATE_BYTES} characters");
|
||||
}
|
||||
|
||||
template
|
||||
});
|
||||
|
||||
// parse string to generic json
|
||||
let mut template: Value = serde_json::from_str(&template)?;
|
||||
eprintln!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&template).expect("re-serialization never fails")
|
||||
);
|
||||
|
||||
let template_obj = template
|
||||
.as_object_mut()
|
||||
.expect("template must be a JSON object");
|
||||
|
||||
// replace zcashd keys that are incompatible with Zebra
|
||||
|
||||
// the longpollid key is in a node-specific format, but this tool doesn't use it,
|
||||
// so we can replace it with a dummy value
|
||||
template_obj["longpollid"] = "0".repeat(LONG_POLL_ID_LENGTH).into();
|
||||
|
||||
// provide dummy keys that Zebra requires but zcashd does not always have
|
||||
|
||||
// the transaction.*.required keys are not used by this tool,
|
||||
// so we can use any value here
|
||||
template_obj["coinbasetxn"]["required"] = true.into();
|
||||
for tx in template_obj["transactions"]
|
||||
.as_array_mut()
|
||||
.expect("transactions must be a JSON array")
|
||||
{
|
||||
tx["required"] = false.into();
|
||||
}
|
||||
|
||||
// the maxtime field is used by this tool
|
||||
// if it is missing, substitute a valid value
|
||||
let current_time: DateTime32 = template_obj["curtime"]
|
||||
.to_string()
|
||||
.parse()
|
||||
.expect("curtime is always a valid DateTime32");
|
||||
|
||||
template_obj.entry("maxtime").or_insert_with(|| {
|
||||
if time_source.uses_max_time() {
|
||||
eprintln!(
|
||||
"maxtime field is missing, using curtime for maxtime: {:?}",
|
||||
current_time,
|
||||
);
|
||||
}
|
||||
|
||||
current_time.timestamp().into()
|
||||
});
|
||||
|
||||
// parse the modified json to template type
|
||||
let template: GetBlockTemplate = serde_json::from_value(template)?;
|
||||
|
||||
// generate proposal according to arguments
|
||||
let proposal = proposal_block_from_template(template, time_source)?;
|
||||
eprintln!("{proposal:#?}");
|
||||
|
||||
let proposal = proposal
|
||||
.zcash_serialize_to_vec()
|
||||
.expect("serialization to Vec never fails");
|
||||
|
||||
println!("{}", hex::encode(proposal));
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -0,0 +1,227 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Gets a block template from a Zcash node instance,
|
||||
# turns it into a block proposal using `block-template-to-proposal`,
|
||||
# and sends it to one or more Zcash node instances (which can include the same node).
|
||||
#
|
||||
# If there are multiple proposal ports, displays a diff of the responses.
|
||||
#
|
||||
# Uses `zcash-cli` with the RPC ports supplied on the command-line.
|
||||
|
||||
function usage()
|
||||
{
|
||||
echo "Usage:"
|
||||
echo "$0 block-template-rpc-port proposal-rpc-port [extra-proposal-rpc-port...] -- [extra-block-template-rpc-json-fields] [extra-proposal-rpc-fields]"
|
||||
}
|
||||
|
||||
# Override the commands used by this script using these environmental variables:
|
||||
ZCASH_CLI="${ZCASH_CLI:-zcash-cli}"
|
||||
DIFF="${DIFF:-diff --unified --color=always}"
|
||||
BLOCK_TEMPLATE_TO_PROPOSAL="${BLOCK_TEMPLATE_TO_PROPOSAL:-block-template-to-proposal}"
|
||||
# time how long a command takes to run
|
||||
TIME="time"
|
||||
# display the current date and time
|
||||
DATE="date --rfc-3339=seconds"
|
||||
|
||||
# Override the settings for this script using these environmental variables:
|
||||
TIME_SOURCES="${TIME_SOURCES:-CurTime MinTime MaxTime ClampedNow}"
|
||||
|
||||
# Process arguments
|
||||
|
||||
if [ $# -lt 2 ]; then
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TEMPLATE_RPC_PORT=$1
|
||||
shift
|
||||
|
||||
PROPOSAL_RPC_PORTS=""
|
||||
while [ -n "${1:-}" ] && [ "${1-}" != "--" ]; do
|
||||
PROPOSAL_RPC_PORTS="$PROPOSAL_RPC_PORTS $1"
|
||||
shift
|
||||
done
|
||||
|
||||
if [ "${1-}" == "--" ]; then
|
||||
shift
|
||||
fi
|
||||
|
||||
TEMPLATE_ARG=""
|
||||
if [ $# -ge 1 ]; then
|
||||
TEMPLATE_ARG="${1:-}"
|
||||
shift
|
||||
fi
|
||||
TEMPLATE_ARG_FULL="{ \"mode\": \"template\" ${TEMPLATE_ARG:+, $TEMPLATE_ARG} }"
|
||||
|
||||
PROPOSAL_ARG=""
|
||||
if [ $# -ge 1 ]; then
|
||||
PROPOSAL_ARG="${1:-}"
|
||||
shift
|
||||
fi
|
||||
PROPOSAL_ARG_NO_DATA="{ \"mode\": \"proposal\", \"data\": \"...\" ${PROPOSAL_ARG:+, $PROPOSAL_ARG} }"
|
||||
|
||||
if [ $# -ge 1 ]; then
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
$DATE
|
||||
|
||||
# Use an easily identified temp directory name,
|
||||
# but fall back to the default temp name if `mktemp` does not understand `--suffix`.
|
||||
ZCASH_RPC_TMP_DIR=$(mktemp --suffix=.block-template-proposal -d 2>/dev/null || mktemp -d)
|
||||
|
||||
TEMPLATE_NODE_RELEASE_INFO="$ZCASH_RPC_TMP_DIR/template-check-getinfo.json"
|
||||
PROPOSAL_NODES_RELEASE_INFO_BASE="$ZCASH_RPC_TMP_DIR/proposal-check-getinfo"
|
||||
|
||||
echo "Checking getblocktemplate node release info..."
|
||||
$ZCASH_CLI -rpcport="$TEMPLATE_RPC_PORT" getinfo > "$TEMPLATE_NODE_RELEASE_INFO"
|
||||
|
||||
TEMPLATE_NODE=$(cat "$TEMPLATE_NODE_RELEASE_INFO" | grep '"subversion"' | \
|
||||
cut -d: -f2 | cut -d/ -f2 | \
|
||||
tr 'A-Z' 'a-z' | sed 's/magicbean/zcashd/ ; s/zebra$/zebrad/')
|
||||
|
||||
echo "Connected to $TEMPLATE_NODE (port $TEMPLATE_RPC_PORT) for getblocktemplate $TEMPLATE_ARG_FULL"
|
||||
|
||||
echo
|
||||
|
||||
echo "Checking proposal nodes release info..."
|
||||
|
||||
for PORT in $PROPOSAL_RPC_PORTS; do
|
||||
PROPOSAL_NODE_RELEASE_INFO=$PROPOSAL_NODES_RELEASE_INFO_BASE.$PORT.json
|
||||
|
||||
$ZCASH_CLI -rpcport="$PORT" getinfo > "$PROPOSAL_NODE_RELEASE_INFO"
|
||||
|
||||
PROPOSAL_NODE=$(cat "$PROPOSAL_NODE_RELEASE_INFO" | grep '"subversion"' | \
|
||||
cut -d: -f2 | cut -d/ -f2 | \
|
||||
tr 'A-Z' 'a-z' | sed 's/magicbean/zcashd/ ; s/zebra$/zebrad/')
|
||||
|
||||
echo "Connected to $PROPOSAL_NODE (port $PORT) for getblocktemplate $PROPOSAL_ARG_NO_DATA"
|
||||
done
|
||||
|
||||
echo
|
||||
|
||||
TEMPLATE_NODE_BLOCKCHAIN_INFO="$ZCASH_RPC_TMP_DIR/template-check-getblockchaininfo.json"
|
||||
PROPOSAL_NODES_BLOCKCHAIN_INFO_BASE="$ZCASH_RPC_TMP_DIR/proposal-check-getblockchaininfo"
|
||||
|
||||
echo "Checking $TEMPLATE_NODE template network and tip height..."
|
||||
$ZCASH_CLI -rpcport="$TEMPLATE_RPC_PORT" getblockchaininfo > "$TEMPLATE_NODE_BLOCKCHAIN_INFO"
|
||||
|
||||
TEMPLATE_NET=$(cat "$TEMPLATE_NODE_BLOCKCHAIN_INFO" | grep '"chain"' | cut -d: -f2 | tr -d ' ,"')
|
||||
TEMPLATE_HEIGHT=$(cat "$TEMPLATE_NODE_BLOCKCHAIN_INFO" | grep '"blocks"' | cut -d: -f2 | tr -d ' ,"')
|
||||
|
||||
echo "Checking proposal nodes network and tip height..."
|
||||
|
||||
for PORT in $PROPOSAL_RPC_PORTS; do
|
||||
PROPOSAL_NODE_BLOCKCHAIN_INFO=$PROPOSAL_NODES_BLOCKCHAIN_INFO_BASE.$PORT.json
|
||||
|
||||
$ZCASH_CLI -rpcport="$PORT" getblockchaininfo > "$PROPOSAL_NODE_BLOCKCHAIN_INFO"
|
||||
|
||||
PROPOSAL_NET=$(cat "$PROPOSAL_NODE_BLOCKCHAIN_INFO" | grep '"chain"' | cut -d: -f2 | tr -d ' ,"')
|
||||
PROPOSAL_HEIGHT=$(cat "$PROPOSAL_NODE_BLOCKCHAIN_INFO" | grep '"blocks"' | cut -d: -f2 | tr -d ' ,"')
|
||||
|
||||
if [ "$PROPOSAL_NET" != "$TEMPLATE_NET" ]; then
|
||||
echo "WARNING: sending block templates between different networks:"
|
||||
echo "$TEMPLATE_NODE (RPC port $TEMPLATE_RPC_PORT) template is on: $TEMPLATE_NET"
|
||||
echo "RPC port $PORT proposal is on: $PROPOSAL_NET"
|
||||
echo
|
||||
fi
|
||||
|
||||
if [ "$PROPOSAL_HEIGHT" -ne "$TEMPLATE_HEIGHT" ]; then
|
||||
echo "WARNING: proposing block templates at different heights:"
|
||||
echo "$TEMPLATE_NODE (RPC port $TEMPLATE_RPC_PORT) template is on: $TEMPLATE_HEIGHT"
|
||||
echo "RPC port $PORT proposal is on: $PROPOSAL_HEIGHT"
|
||||
echo
|
||||
fi
|
||||
done
|
||||
|
||||
echo
|
||||
|
||||
TEMPLATE_NODE_TEMPLATE_RESPONSE="$ZCASH_RPC_TMP_DIR/template-getblocktemplate-template.json"
|
||||
|
||||
echo "getblocktemplate template request ($TEMPLATE_NODE port $TEMPLATE_RPC_PORT):"
|
||||
echo "getblocktemplate $TEMPLATE_ARG_FULL"
|
||||
echo
|
||||
|
||||
echo "Querying $TEMPLATE_NODE $TEMPLATE_NET chain at height >=$TEMPLATE_HEIGHT..."
|
||||
$DATE
|
||||
$TIME $ZCASH_CLI -rpcport="$TEMPLATE_RPC_PORT" getblocktemplate "$TEMPLATE_ARG_FULL" > "$TEMPLATE_NODE_TEMPLATE_RESPONSE"
|
||||
|
||||
echo "Block template data is in $TEMPLATE_NODE_TEMPLATE_RESPONSE"
|
||||
#cat "$TEMPLATE_NODE_TEMPLATE_RESPONSE"
|
||||
|
||||
echo
|
||||
|
||||
PROPOSAL_DATA_BASE="$ZCASH_RPC_TMP_DIR/proposal-data"
|
||||
|
||||
echo "Turning the template into block proposal data using $BLOCK_TEMPLATE_TO_PROPOSAL..."
|
||||
|
||||
for TIME_SOURCE in $TIME_SOURCES; do
|
||||
PROPOSAL_DATA="$PROPOSAL_DATA_BASE.$TIME_SOURCE.json"
|
||||
PROPOSAL_DEBUG="$PROPOSAL_DATA_BASE.$TIME_SOURCE.debug"
|
||||
|
||||
echo -n '{ "mode": "proposal", ' > "$PROPOSAL_DATA"
|
||||
echo -n '"data": "' >> "$PROPOSAL_DATA"
|
||||
cat "$TEMPLATE_NODE_TEMPLATE_RESPONSE" | \
|
||||
$BLOCK_TEMPLATE_TO_PROPOSAL \
|
||||
2> "$PROPOSAL_DEBUG" | \
|
||||
(tr -d '\r\n' || true) \
|
||||
>> "$PROPOSAL_DATA"
|
||||
echo -n '"' >> "$PROPOSAL_DATA"
|
||||
echo -n "${PROPOSAL_ARG:+, $PROPOSAL_ARG} }" >> "$PROPOSAL_DATA"
|
||||
done
|
||||
|
||||
echo "Block proposal data is in $PROPOSAL_DATA_BASE*"
|
||||
#cat "$PROPOSAL_DATA_BASE"*
|
||||
|
||||
echo
|
||||
echo
|
||||
|
||||
echo "getblocktemplate proposal submissions:"
|
||||
echo "getblocktemplate $PROPOSAL_ARG_NO_DATA"
|
||||
$DATE
|
||||
echo
|
||||
|
||||
PROPOSAL_NODES_PROPOSAL_RESPONSE_BASE="$ZCASH_RPC_TMP_DIR/proposal-check-getblocktemplate-proposal"
|
||||
PROPOSAL_NODES_PROPOSAL_RESPONSE_LIST=""
|
||||
|
||||
for TIME_SOURCE in $TIME_SOURCES; do
|
||||
PROPOSAL_DATA="$PROPOSAL_DATA_BASE.$TIME_SOURCE.json"
|
||||
|
||||
for PORT in $PROPOSAL_RPC_PORTS; do
|
||||
PROPOSAL_NODE_PROPOSAL_RESPONSE=$PROPOSAL_NODES_PROPOSAL_RESPONSE_BASE.$TIME_SOURCE.$PORT.json
|
||||
PROPOSAL_NODES_PROPOSAL_RESPONSE_LIST="${PROPOSAL_NODES_PROPOSAL_RESPONSE_LIST:+$PROPOSAL_NODES_PROPOSAL_RESPONSE_LIST }$PROPOSAL_NODE_PROPOSAL_RESPONSE"
|
||||
|
||||
# read the proposal data from a file, to avoid command-line length limits
|
||||
cat "$PROPOSAL_DATA" | \
|
||||
$TIME $ZCASH_CLI -rpcport="$PORT" -stdin getblocktemplate \
|
||||
> "$PROPOSAL_NODE_PROPOSAL_RESPONSE" || \
|
||||
echo "$ZCASH_CLI -rpcport=$PORT exited with an error"
|
||||
done
|
||||
done
|
||||
|
||||
echo
|
||||
|
||||
echo "Proposal response diffs between ports $PROPOSAL_RPC_PORTS and time sources $TIME_SOURCES:"
|
||||
|
||||
$DIFF --from-file=$PROPOSAL_NODES_PROPOSAL_RESPONSE_LIST && \
|
||||
echo "getblocktemplate proposal responses were identical"
|
||||
|
||||
echo
|
||||
|
||||
EXIT_STATUS=0
|
||||
for RESPONSE in $PROPOSAL_NODES_PROPOSAL_RESPONSE_LIST; do
|
||||
if [ -s "$RESPONSE" ]; then
|
||||
echo "Node said proposal was invalid, error response from $RESPONSE:"
|
||||
cat "$RESPONSE"
|
||||
EXIT_STATUS=1
|
||||
else
|
||||
echo "Node said proposal was valid, empty success response in $RESPONSE"
|
||||
fi
|
||||
done
|
||||
|
||||
$DATE
|
||||
|
||||
exit $EXIT_STATUS
|
|
@ -5,22 +5,14 @@
|
|||
//!
|
||||
//! After finishing the sync, it will call getblocktemplate.
|
||||
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use std::time::Duration;
|
||||
|
||||
use color_eyre::eyre::{eyre, Context, Result};
|
||||
|
||||
use zebra_chain::{
|
||||
block::{self, Block, Height},
|
||||
parameters::Network,
|
||||
serialization::{ZcashDeserializeInto, ZcashSerialize},
|
||||
work::equihash::Solution,
|
||||
};
|
||||
use zebra_rpc::methods::{
|
||||
get_block_template_rpcs::{
|
||||
get_block_template::{GetBlockTemplate, ProposalResponse},
|
||||
types::default_roots::DefaultRoots,
|
||||
},
|
||||
GetBlockHash,
|
||||
use zebra_chain::{parameters::Network, serialization::ZcashSerialize};
|
||||
use zebra_rpc::methods::get_block_template_rpcs::{
|
||||
get_block_template::{proposal::TimeSource, ProposalResponse},
|
||||
types::get_block_template::proposal_block_from_template,
|
||||
};
|
||||
|
||||
use crate::common::{
|
||||
|
@ -153,7 +145,12 @@ async fn try_validate_block_template(client: &RPCRequestClient) -> Result<()> {
|
|||
// Propose a new block with an empty solution and nonce field
|
||||
tracing::info!("calling getblocktemplate with a block proposal...",);
|
||||
|
||||
for proposal_block in proposal_block_from_template(response_json_result)? {
|
||||
// TODO: update this to use all valid time sources in the next PR
|
||||
#[allow(clippy::single_element_loop)]
|
||||
for proposal_block in [proposal_block_from_template(
|
||||
response_json_result,
|
||||
TimeSource::CurTime,
|
||||
)?] {
|
||||
let raw_proposal_block = hex::encode(proposal_block.zcash_serialize_to_vec()?);
|
||||
|
||||
let json_result = client
|
||||
|
@ -181,53 +178,3 @@ async fn try_validate_block_template(client: &RPCRequestClient) -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Make block proposals from [`GetBlockTemplate`]
|
||||
///
|
||||
/// Returns an array of 3 block proposals using `curtime`, `mintime`, and `maxtime`
|
||||
/// for their `block.header.time` fields.
|
||||
#[allow(dead_code)]
|
||||
fn proposal_block_from_template(
|
||||
GetBlockTemplate {
|
||||
version,
|
||||
height,
|
||||
previous_block_hash: GetBlockHash(previous_block_hash),
|
||||
default_roots:
|
||||
DefaultRoots {
|
||||
merkle_root,
|
||||
block_commitments_hash,
|
||||
..
|
||||
},
|
||||
bits: difficulty_threshold,
|
||||
coinbase_txn,
|
||||
transactions: tx_templates,
|
||||
cur_time,
|
||||
min_time,
|
||||
max_time,
|
||||
..
|
||||
}: GetBlockTemplate,
|
||||
) -> Result<[Block; 3]> {
|
||||
if Height(height) > Height::MAX {
|
||||
Err(eyre!("height field must be lower than Height::MAX"))?;
|
||||
};
|
||||
|
||||
let mut transactions = vec![coinbase_txn.data.as_ref().zcash_deserialize_into()?];
|
||||
|
||||
for tx_template in tx_templates {
|
||||
transactions.push(tx_template.data.as_ref().zcash_deserialize_into()?);
|
||||
}
|
||||
|
||||
Ok([cur_time, min_time, max_time].map(|time| Block {
|
||||
header: Arc::new(block::Header {
|
||||
version,
|
||||
previous_block_hash,
|
||||
merkle_root,
|
||||
commitment_bytes: block_commitments_hash.bytes_in_serialized_order().into(),
|
||||
time: time.into(),
|
||||
difficulty_threshold,
|
||||
nonce: [0; 32].into(),
|
||||
solution: Solution::for_proposal(),
|
||||
}),
|
||||
transactions: transactions.clone(),
|
||||
}))
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue