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:
teor 2023-01-18 12:11:15 +10:00 committed by GitHub
parent 256b1c0008
commit 6a06cbf3ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 585 additions and 72 deletions

1
Cargo.lock generated
View File

@ -5633,6 +5633,7 @@ dependencies = [
"tracing-subscriber 0.3.16",
"zebra-chain",
"zebra-node-services",
"zebra-rpc",
]
[[package]]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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(())
}

View File

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

View File

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