From a3dfddcffbec48188a3506390ce05b7d3169e537 Mon Sep 17 00:00:00 2001 From: teor Date: Tue, 17 Jan 2023 15:06:06 +1000 Subject: [PATCH 01/32] Revert "Update code that we're going to delete in the next PR anyway" This reverts commit 1fed70da9ef6f891d6465bcfc0ec4bacd9e81948. --- .../common/get_block_template_rpcs/get_block_template.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zebrad/tests/common/get_block_template_rpcs/get_block_template.rs b/zebrad/tests/common/get_block_template_rpcs/get_block_template.rs index 93c59670d60..b3509428de6 100644 --- a/zebrad/tests/common/get_block_template_rpcs/get_block_template.rs +++ b/zebrad/tests/common/get_block_template_rpcs/get_block_template.rs @@ -222,10 +222,10 @@ fn proposal_block_from_template( version, previous_block_hash, merkle_root, - commitment_bytes: block_commitments_hash.bytes_in_serialized_order().into(), + commitment_bytes: block_commitments_hash.into(), time: time.into(), difficulty_threshold, - nonce: [0; 32].into(), + nonce: [0; 32], solution: Solution::for_proposal(), }), transactions: transactions.clone(), From 1d2d54c3447560f7edd9f0038ea8845b10fa8fb4 Mon Sep 17 00:00:00 2001 From: teor Date: Wed, 11 Jan 2023 14:37:48 +1000 Subject: [PATCH 02/32] Initial zcash-test-block-template script without block proposal construction --- .../zcash-rpc-block-template-to-proposal | 174 ++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100755 zebra-utils/zcash-rpc-block-template-to-proposal diff --git a/zebra-utils/zcash-rpc-block-template-to-proposal b/zebra-utils/zcash-rpc-block-template-to-proposal new file mode 100755 index 00000000000..b454992f4b6 --- /dev/null +++ b/zebra-utils/zcash-rpc-block-template-to-proposal @@ -0,0 +1,174 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# Gets a block template from a Zcash node instance, +# turns it into a block 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}" +JQ="${JQ:-jq}" + +# 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 + +# 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..." +time $ZCASH_CLI -rpcport="$TEMPLATE_RPC_PORT" getblocktemplate "$TEMPLATE_ARG_FULL" > "$TEMPLATE_NODE_TEMPLATE_RESPONSE" +echo + +cat "$TEMPLATE_NODE_TEMPLATE_RESPONSE" +echo "Turning the template into block proposal data using $JQ..." +# TODO: build a header, transaction count, and transaction list +PROPOSAL_ARG_FULL=$PROPOSAL_ARG_NO_DATA + +echo "getblocktemplate proposal submissions:" +echo "getblocktemplate $PROPOSAL_ARG_NO_DATA" +echo + +PROPOSAL_NODES_PROPOSAL_RESPONSE_BASE="$ZCASH_RPC_TMP_DIR/proposal-check-getblocktemplate-proposal" +PROPOSAL_NODES_PROPOSAL_RESPONSE_LIST="" + +for PORT in $PROPOSAL_RPC_PORTS; do + PROPOSAL_NODE_PROPOSAL_RESPONSE=$PROPOSAL_NODES_PROPOSAL_RESPONSE_BASE.$PORT.json + PROPOSAL_NODES_PROPOSAL_RESPONSE_LIST="$PROPOSAL_NODES_PROPOSAL_RESPONSE_LIST $PROPOSAL_NODE_PROPOSAL_RESPONSE" + + time $ZCASH_CLI -rpcport="$PORT" getblocktemplate "$PROPOSAL_ARG_FULL" > "$PROPOSAL_NODE_PROPOSAL_RESPONSE" +done + +echo + +echo "Proposal response diffs between ports $PROPOSAL_RPC_PORTS:" + +$DIFF $PROPOSAL_NODES_PROPOSAL_RESPONSE_LIST \ + && ( \ + echo "getblocktemplate proposal responses were identical"; \ + echo ; \ + echo "Final response from $PROPOSAL_NODE_PROPOSAL_RESPONSE:"; \ + cat "$PROPOSAL_NODE_PROPOSAL_RESPONSE"; \ + ) + +EXIT_STATUS=$? From 5ec5a07327063cf4180247e9dce5fe2f72cbc2d4 Mon Sep 17 00:00:00 2001 From: teor Date: Wed, 11 Jan 2023 15:48:59 +1000 Subject: [PATCH 03/32] Try creating block data using jq and printf --- .../zcash-rpc-block-template-to-proposal | 62 +++++++++++++++---- 1 file changed, 50 insertions(+), 12 deletions(-) diff --git a/zebra-utils/zcash-rpc-block-template-to-proposal b/zebra-utils/zcash-rpc-block-template-to-proposal index b454992f4b6..4d6d93d31ee 100755 --- a/zebra-utils/zcash-rpc-block-template-to-proposal +++ b/zebra-utils/zcash-rpc-block-template-to-proposal @@ -32,8 +32,10 @@ TEMPLATE_RPC_PORT=$1 shift PROPOSAL_RPC_PORTS="" +PROPOSAL_RPC_PORT_COUNT=0 while [ -n "${1:-}" ] && [ "${1-}" != "--" ]; do PROPOSAL_RPC_PORTS="$PROPOSAL_RPC_PORTS $1" + PROPOSAL_RPC_PORT_COUNT=$((PROPOSAL_RPC_PORT_COUNT + 1)) shift done @@ -138,12 +140,41 @@ echo echo "Querying $TEMPLATE_NODE $TEMPLATE_NET chain at height >=$TEMPLATE_HEIGHT..." 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 -cat "$TEMPLATE_NODE_TEMPLATE_RESPONSE" +PROPOSAL_DATA="$ZCASH_RPC_TMP_DIR/proposal-data.json" + echo "Turning the template into block proposal data using $JQ..." -# TODO: build a header, transaction count, and transaction list -PROPOSAL_ARG_FULL=$PROPOSAL_ARG_NO_DATA + +# Block header +# See https://zips.z.cash/protocol/protocol.pdf#blockheader for details +cat "$TEMPLATE_NODE_TEMPLATE_RESPONSE" | \ + $JQ -r '. | "printf 0\(.version)000000\(.previousblockhash)\(.defaultroots.merkleroot)\(.defaultroots.blockcommitmentshash)%x\(.bits)%064xfd4005%02688x \(.curtime) 0 0"' | \ + sh \ + > "$PROPOSAL_DATA" + +# Transaction list +# See https://developer.bitcoin.org/reference/block_chain.html#serialized-blocks for details +# This encoding only works up to 252 transactions, because it only implements 1 byte compact size +# https://developer.bitcoin.org/reference/transactions.html#compactsize-unsigned-integers +cat "$TEMPLATE_NODE_TEMPLATE_RESPONSE" | \ + $JQ -r '. | "printf %02x\(.coinbasetxn.data)\(.transactions | .[]? | {data} | join("")) \(.transactions | length + 1)"' | \ + sh \ + >> "$PROPOSAL_DATA" + +# TODO: put this in a file if it exceeds the bash variable size limit +PROPOSAL_ARG_FULL="{ \"mode\": \"proposal\", \"data\": \"$(cat $PROPOSAL_DATA)\" ${PROPOSAL_ARG:+, $PROPOSAL_ARG} }" + +echo "Block proposal data is in $PROPOSAL_DATA" +# TODO: disable verbose block data +cat "$PROPOSAL_DATA" + +echo +echo echo "getblocktemplate proposal submissions:" echo "getblocktemplate $PROPOSAL_ARG_NO_DATA" @@ -156,19 +187,26 @@ for PORT in $PROPOSAL_RPC_PORTS; do PROPOSAL_NODE_PROPOSAL_RESPONSE=$PROPOSAL_NODES_PROPOSAL_RESPONSE_BASE.$PORT.json PROPOSAL_NODES_PROPOSAL_RESPONSE_LIST="$PROPOSAL_NODES_PROPOSAL_RESPONSE_LIST $PROPOSAL_NODE_PROPOSAL_RESPONSE" + # TODO: try modifying $PROPOSAL_DATA to contain the whole jsonrequestobject, + # and then call: + # xargs --max-chars=2000200 --arg-file="$PROPOSAL_DATA" time $ZCASH_CLI -rpcport="$PORT" getblocktemplate time $ZCASH_CLI -rpcport="$PORT" getblocktemplate "$PROPOSAL_ARG_FULL" > "$PROPOSAL_NODE_PROPOSAL_RESPONSE" done echo -echo "Proposal response diffs between ports $PROPOSAL_RPC_PORTS:" +if [ "$PROPOSAL_RPC_PORT_COUNT" -gt 1 ]; then + echo "Proposal response diffs between ports $PROPOSAL_RPC_PORTS:" -$DIFF $PROPOSAL_NODES_PROPOSAL_RESPONSE_LIST \ - && ( \ - echo "getblocktemplate proposal responses were identical"; \ - echo ; \ - echo "Final response from $PROPOSAL_NODE_PROPOSAL_RESPONSE:"; \ - cat "$PROPOSAL_NODE_PROPOSAL_RESPONSE"; \ - ) + $DIFF $PROPOSAL_NODES_PROPOSAL_RESPONSE_LIST \ + && ( \ + echo "getblocktemplate proposal responses were identical"; \ + echo ; \ + echo "Final response from $PROPOSAL_NODE_PROPOSAL_RESPONSE:"; \ + cat "$PROPOSAL_NODE_PROPOSAL_RESPONSE"; \ + ) +else + echo "Proposal response from port $PROPOSAL_RPC_PORTS:" -EXIT_STATUS=$? + cat $PROPOSAL_NODES_PROPOSAL_RESPONSE_LIST +fi From c0b1189fed5196fa8bbb62c1d4c0f5252597d280 Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 13 Jan 2023 09:00:40 +1000 Subject: [PATCH 04/32] Move proposal_block_from_template() to zebra-rpc and remove eyre dependency --- .../types/get_block_template.rs | 24 +++++-- .../types/get_block_template/proposal.rs | 45 +++++++++++- .../get_block_template.rs | 68 ++----------------- 3 files changed, 66 insertions(+), 71 deletions(-) diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template.rs index f39c4bb20da..3a8860c539c 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template.rs @@ -1,14 +1,22 @@ //! The `GetBlockTempate` type is the output of the `getblocktemplate` RPC method in the //! default 'template' mode. See [`ProposalResponse`] for the output in 'proposal' mode. +use std::sync::Arc; + +use chrono::Utc; + use zebra_chain::{ amount, - block::{ChainHistoryBlockTxAuthCommitmentHash, MAX_BLOCK_BYTES, ZCASH_BLOCK_VERSION}, - parameters::Network, - serialization::DateTime32, + block::{ + self, Block, ChainHistoryBlockTxAuthCommitmentHash, Height, MAX_BLOCK_BYTES, + ZCASH_BLOCK_VERSION, + }, + serialization::{DateTime32, SerializationError, ZcashDeserializeInto}, transaction::VerifiedUnminedTx, - transparent, - work::difficulty::{CompactDifficulty, ExpandedDifficulty}, + work::{ + difficulty::{CompactDifficulty, ExpandedDifficulty}, + equihash::Solution, + }, }; use zebra_consensus::MAX_BLOCK_SIGOPS; use zebra_state::GetBlockTemplateChainInfo; @@ -296,3 +304,9 @@ pub enum Response { /// `getblocktemplate` RPC request in proposal mode. ProposalMode(ProposalResponse), } + +impl From for Response { + fn from(template: GetBlockTemplate) -> Self { + Self::TemplateMode(Box::new(template)) + } +} diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs index 5434390c648..fd82ee17ff2 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs @@ -52,8 +52,47 @@ impl From for Response { } } -impl From for Response { - fn from(template: GetBlockTemplate) -> Self { - Self::TemplateMode(Box::new(template)) +/// Returns a block proposal generated from a [`GetBlockTemplate`] RPC response. +pub 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, + .. + }: GetBlockTemplate, +) -> Result { + if Height(height) > Height::MAX { + Err(SerializationError::Parse( + "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(Block { + header: Arc::new(block::Header { + version, + previous_block_hash, + merkle_root, + commitment_bytes: block_commitments_hash.into(), + time: Utc::now(), + difficulty_threshold, + nonce: [0; 32], + solution: Solution::default(), + }), + transactions, + }) } diff --git a/zebrad/tests/common/get_block_template_rpcs/get_block_template.rs b/zebrad/tests/common/get_block_template_rpcs/get_block_template.rs index b3509428de6..819d7116e7b 100644 --- a/zebrad/tests/common/get_block_template_rpcs/get_block_template.rs +++ b/zebrad/tests/common/get_block_template_rpcs/get_block_template.rs @@ -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::{ @@ -181,53 +173,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.into(), - time: time.into(), - difficulty_threshold, - nonce: [0; 32], - solution: Solution::for_proposal(), - }), - transactions: transactions.clone(), - })) -} From 97d11a9a9d41d6b91cd460cda9b144611990cc03 Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 13 Jan 2023 09:01:18 +1000 Subject: [PATCH 05/32] Implement FromStr for DateTime32 and Duration32 --- zebra-chain/src/serialization/date_time.rs | 28 ++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/zebra-chain/src/serialization/date_time.rs b/zebra-chain/src/serialization/date_time.rs index 5b2c0f73837..def9dfa5ef5 100644 --- a/zebra-chain/src/serialization/date_time.rs +++ b/zebra-chain/src/serialization/date_time.rs @@ -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 { + Ok(DateTime32 { + timestamp: s.parse()?, + }) + } +} + +impl FromStr for Duration32 { + type Err = ParseIntError; + + fn from_str(s: &str) -> Result { + Ok(Duration32 { + seconds: s.parse()?, + }) + } +} + impl ZcashSerialize for DateTime32 { fn zcash_serialize(&self, mut writer: W) -> Result<(), std::io::Error> { writer.write_u32::(self.timestamp) From 14014f488fd276c9cc22451071ab0a6d22dd4e8c Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 13 Jan 2023 09:18:05 +1000 Subject: [PATCH 06/32] Basic block-template-to-proposal command without time source handling --- Cargo.lock | 1 + zebra-utils/Cargo.toml | 1 + .../bin/block-template-to-proposal/args.rs | 68 +++++++++++++++++++ .../bin/block-template-to-proposal/main.rs | 64 +++++++++++++++++ 4 files changed, 134 insertions(+) create mode 100644 zebra-utils/src/bin/block-template-to-proposal/args.rs create mode 100644 zebra-utils/src/bin/block-template-to-proposal/main.rs diff --git a/Cargo.lock b/Cargo.lock index 17bf1ce16e4..fbf772951c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5633,6 +5633,7 @@ dependencies = [ "tracing-subscriber 0.3.16", "zebra-chain", "zebra-node-services", + "zebra-rpc", ] [[package]] diff --git a/zebra-utils/Cargo.toml b/zebra-utils/Cargo.toml index 24379486deb..ada53b8e24b 100644 --- a/zebra-utils/Cargo.toml +++ b/zebra-utils/Cargo.toml @@ -19,5 +19,6 @@ serde_json = "1.0.91" tracing-error = "0.2.0" tracing-subscriber = "0.3.16" +zebra-rpc = { path = "../zebra-rpc", features = ["getblocktemplate-rpcs"] } zebra-node-services = { path = "../zebra-node-services" } zebra-chain = { path = "../zebra-chain" } diff --git a/zebra-utils/src/bin/block-template-to-proposal/args.rs b/zebra-utils/src/bin/block-template-to-proposal/args.rs new file mode 100644 index 00000000000..a850b2e5c06 --- /dev/null +++ b/zebra-utils/src/bin/block-template-to-proposal/args.rs @@ -0,0 +1,68 @@ +//! block-template-to-proposal arguments +//! +//! For usage please refer to the program help: `block-template-to-proposal --help` + +use std::str::FromStr; + +use structopt::StructOpt; + +use zebra_chain::{serialization::DateTime32, BoxError}; + +/// The source of the time in the block proposal header. +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum TimeSource { + /// The `curtime` field in the template. + 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 raw supplied time, ignoring the `mintime` and `maxtime` in the template. + /// + /// Warning: this can create an invalid block proposal. + Raw(DateTime32), +} + +impl FromStr for TimeSource { + type Err = BoxError; + + fn from_str(s: &str) -> Result { + use TimeSource::*; + + match s.to_lowercase().as_str() { + "curtime" => Ok(CurTime), + "mintime" => Ok(MinTime), + "maxtime" => Ok(MaxTime), + // "raw"u32 + s if s.strip_prefix("raw").is_some() => { + Ok(Raw(s.strip_prefix("raw").unwrap().parse()?)) + } + // "clamped"u32 or just u32 + _ => Ok(Clamped(s.strip_prefix("clamped").unwrap_or(s).parse()?)), + } + } +} + +/// 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, +} diff --git a/zebra-utils/src/bin/block-template-to-proposal/main.rs b/zebra-utils/src/bin/block-template-to-proposal/main.rs new file mode 100644 index 00000000000..6880d116fbe --- /dev/null +++ b/zebra-utils/src/bin/block-template-to-proposal/main.rs @@ -0,0 +1,64 @@ +//! 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 structopt::StructOpt; + +use zebra_chain::serialization::ZcashSerialize; +use zebra_rpc::methods::get_block_template_rpcs::{ + get_block_template::proposal_block_from_template, types::get_block_template::GetBlockTemplate, +}; +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 json + let template: GetBlockTemplate = serde_json::from_str(&template)?; + eprintln!("{template:?}"); + + // generate proposal according to arguments + let proposal = proposal_block_from_template(template)?; + eprintln!("{proposal:?}"); + + let proposal = proposal + .zcash_serialize_to_vec() + .expect("serialization to Vec never fails"); + + println!("{}", hex::encode(proposal)); + + Ok(()) +} From d0dbe0eabdb9329335693357ef758468aa865488 Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 13 Jan 2023 09:28:16 +1000 Subject: [PATCH 07/32] Move block proposal code into its own module --- .../types/get_block_template.rs | 28 ++------ .../types/get_block_template/proposal.rs | 67 +++++++++++++++---- .../bin/block-template-to-proposal/args.rs | 45 +------------ 3 files changed, 62 insertions(+), 78 deletions(-) diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template.rs index 3a8860c539c..29689ed530e 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template.rs @@ -1,22 +1,12 @@ //! The `GetBlockTempate` type is the output of the `getblocktemplate` RPC method in the //! default 'template' mode. See [`ProposalResponse`] for the output in 'proposal' mode. -use std::sync::Arc; - -use chrono::Utc; - use zebra_chain::{ amount, - block::{ - self, Block, ChainHistoryBlockTxAuthCommitmentHash, Height, MAX_BLOCK_BYTES, - ZCASH_BLOCK_VERSION, - }, - serialization::{DateTime32, SerializationError, ZcashDeserializeInto}, + block::{ChainHistoryBlockTxAuthCommitmentHash, MAX_BLOCK_BYTES, ZCASH_BLOCK_VERSION}, + serialization::DateTime32, transaction::VerifiedUnminedTx, - work::{ - difficulty::{CompactDifficulty, ExpandedDifficulty}, - equihash::Solution, - }, + work::difficulty::{CompactDifficulty, ExpandedDifficulty}, }; use zebra_consensus::MAX_BLOCK_SIGOPS; use zebra_state::GetBlockTemplateChainInfo; @@ -38,8 +28,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)] @@ -294,7 +284,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 { @@ -304,9 +294,3 @@ pub enum Response { /// `getblocktemplate` RPC request in proposal mode. ProposalMode(ProposalResponse), } - -impl From for Response { - fn from(template: GetBlockTemplate) -> Self { - Self::TemplateMode(Box::new(template)) - } -} diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs index fd82ee17ff2..670ebc1ecef 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs @@ -1,11 +1,25 @@ +//! 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 chrono::Utc; +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, + }, + GetBlockHash, +}; /// Error response to a `getblocktemplate` RPC request in proposal mode. -/// -/// See -#[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 +29,7 @@ pub enum ProposalRejectReason { /// Response to a `getblocktemplate` RPC request in proposal mode. /// /// See -#[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`. @@ -40,15 +54,44 @@ impl From for ProposalResponse { } } -impl From for Response { - fn from(error_response: ProposalRejectReason) -> Self { - Self::ProposalMode(ProposalResponse::from(error_response)) - } +/// The source of the time in the block proposal header. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum TimeSource { + /// The `curtime` field in the template. + 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 raw supplied time, ignoring the `mintime` and `maxtime` in the template. + /// + /// Warning: this can create an invalid block proposal. + Raw(DateTime32), } -impl From for Response { - fn from(proposal_response: ProposalResponse) -> Self { - Self::ProposalMode(proposal_response) +impl FromStr for TimeSource { + type Err = ParseIntError; + + fn from_str(s: &str) -> Result { + use TimeSource::*; + + match s.to_lowercase().as_str() { + "curtime" => Ok(CurTime), + "mintime" => Ok(MinTime), + "maxtime" => Ok(MaxTime), + // "raw"u32 + s if s.strip_prefix("raw").is_some() => { + Ok(Raw(s.strip_prefix("raw").unwrap().parse()?)) + } + // "clamped"u32 or just u32 + _ => Ok(Clamped(s.strip_prefix("clamped").unwrap_or(s).parse()?)), + } } } diff --git a/zebra-utils/src/bin/block-template-to-proposal/args.rs b/zebra-utils/src/bin/block-template-to-proposal/args.rs index a850b2e5c06..3378a784a5d 100644 --- a/zebra-utils/src/bin/block-template-to-proposal/args.rs +++ b/zebra-utils/src/bin/block-template-to-proposal/args.rs @@ -2,52 +2,9 @@ //! //! For usage please refer to the program help: `block-template-to-proposal --help` -use std::str::FromStr; - use structopt::StructOpt; -use zebra_chain::{serialization::DateTime32, BoxError}; - -/// The source of the time in the block proposal header. -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum TimeSource { - /// The `curtime` field in the template. - 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 raw supplied time, ignoring the `mintime` and `maxtime` in the template. - /// - /// Warning: this can create an invalid block proposal. - Raw(DateTime32), -} - -impl FromStr for TimeSource { - type Err = BoxError; - - fn from_str(s: &str) -> Result { - use TimeSource::*; - - match s.to_lowercase().as_str() { - "curtime" => Ok(CurTime), - "mintime" => Ok(MinTime), - "maxtime" => Ok(MaxTime), - // "raw"u32 - s if s.strip_prefix("raw").is_some() => { - Ok(Raw(s.strip_prefix("raw").unwrap().parse()?)) - } - // "clamped"u32 or just u32 - _ => Ok(Clamped(s.strip_prefix("clamped").unwrap_or(s).parse()?)), - } - } -} +use zebra_rpc::methods::get_block_template_rpcs::get_block_template::proposal::TimeSource; /// block-template-to-proposal arguments #[derive(Clone, Debug, Eq, PartialEq, StructOpt)] From cd2127910d22e445175d071a7bfa7e59b6a480a2 Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 13 Jan 2023 09:56:33 +1000 Subject: [PATCH 08/32] Use time source in block-template-to-proposal --- .../types/get_block_template/proposal.rs | 57 ++++++++++++++++--- .../bin/block-template-to-proposal/main.rs | 2 +- 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs index 670ebc1ecef..e2c50324447 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs @@ -4,7 +4,6 @@ use std::{num::ParseIntError, str::FromStr, sync::Arc}; -use chrono::Utc; use zebra_chain::{ block::{self, Block, Height}, serialization::{DateTime32, SerializationError, ZcashDeserializeInto}, @@ -58,6 +57,7 @@ impl From for ProposalResponse { #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum TimeSource { /// The `curtime` field in the template. + /// This is the default time source. CurTime, /// The `mintime` field in the template. @@ -69,10 +69,41 @@ pub enum TimeSource { /// 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(), + } + } +} + +impl Default for TimeSource { + fn default() -> Self { + TimeSource::CurTime + } } impl FromStr for TimeSource { @@ -85,6 +116,8 @@ impl FromStr for TimeSource { "curtime" => Ok(CurTime), "mintime" => Ok(MinTime), "maxtime" => Ok(MaxTime), + "clampednow" => Ok(ClampedNow), + "rawnow" => Ok(RawNow), // "raw"u32 s if s.strip_prefix("raw").is_some() => { Ok(Raw(s.strip_prefix("raw").unwrap().parse()?)) @@ -96,8 +129,13 @@ impl FromStr for TimeSource { } /// 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( - GetBlockTemplate { + template: GetBlockTemplate, + time_source: impl Into>, +) -> Result { + let GetBlockTemplate { version, height, previous_block_hash: GetBlockHash(previous_block_hash), @@ -108,17 +146,22 @@ pub fn proposal_block_from_template( .. }, bits: difficulty_threshold, - coinbase_txn, - transactions: tx_templates, + ref coinbase_txn, + transactions: ref tx_templates, .. - }: GetBlockTemplate, -) -> Result { + } = 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 { @@ -131,7 +174,7 @@ pub fn proposal_block_from_template( previous_block_hash, merkle_root, commitment_bytes: block_commitments_hash.into(), - time: Utc::now(), + time: time.into(), difficulty_threshold, nonce: [0; 32], solution: Solution::default(), diff --git a/zebra-utils/src/bin/block-template-to-proposal/main.rs b/zebra-utils/src/bin/block-template-to-proposal/main.rs index 6880d116fbe..af7fb42d35c 100644 --- a/zebra-utils/src/bin/block-template-to-proposal/main.rs +++ b/zebra-utils/src/bin/block-template-to-proposal/main.rs @@ -51,7 +51,7 @@ fn main() -> Result<()> { eprintln!("{template:?}"); // generate proposal according to arguments - let proposal = proposal_block_from_template(template)?; + let proposal = proposal_block_from_template(template, time_source)?; eprintln!("{proposal:?}"); let proposal = proposal From fa663768680492e4e5ae956f4e52f6f522821333 Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 13 Jan 2023 14:59:38 +1000 Subject: [PATCH 09/32] Make block-template-to-proposal require the getblocktemplate-rpcs feature --- zebra-utils/Cargo.toml | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/zebra-utils/Cargo.toml b/zebra-utils/Cargo.toml index ada53b8e24b..cf35a55b2e1 100644 --- a/zebra-utils/Cargo.toml +++ b/zebra-utils/Cargo.toml @@ -4,9 +4,28 @@ authors = ["Zcash Foundation "] 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" +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. @@ -19,6 +38,8 @@ serde_json = "1.0.91" tracing-error = "0.2.0" tracing-subscriber = "0.3.16" -zebra-rpc = { path = "../zebra-rpc", features = ["getblocktemplate-rpcs"] } zebra-node-services = { path = "../zebra-node-services" } zebra-chain = { path = "../zebra-chain" } + +# Experimental feature getblocktemplate-rpcs +zebra-rpc = { path = "../zebra-rpc", optional = true } From 21ad68ea9ff78a07a9df3e9f76de9f9734d8d697 Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 13 Jan 2023 15:00:24 +1000 Subject: [PATCH 10/32] Use block-template-to-proposal in the test script zcash-rpc-block-template-to-proposal --- .../zcash-rpc-block-template-to-proposal | 23 +++++-------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/zebra-utils/zcash-rpc-block-template-to-proposal b/zebra-utils/zcash-rpc-block-template-to-proposal index 4d6d93d31ee..64072f63fcd 100755 --- a/zebra-utils/zcash-rpc-block-template-to-proposal +++ b/zebra-utils/zcash-rpc-block-template-to-proposal @@ -3,7 +3,7 @@ set -euo pipefail # Gets a block template from a Zcash node instance, -# turns it into a block proposal, +# 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. @@ -19,7 +19,7 @@ function usage() # Override the commands used by this script using these environmental variables: ZCASH_CLI="${ZCASH_CLI:-zcash-cli}" DIFF="${DIFF:-diff --unified --color=always}" -JQ="${JQ:-jq}" +BLOCK_TEMPLATE_TO_PROPOSAL="${BLOCK_TEMPLATE_TO_PROPOSAL:-block-template-to-proposal}" # Process arguments @@ -148,23 +148,12 @@ echo PROPOSAL_DATA="$ZCASH_RPC_TMP_DIR/proposal-data.json" -echo "Turning the template into block proposal data using $JQ..." +echo "Turning the template into block proposal data using $BLOCK_TEMPLATE_TO_PROPOSAL..." -# Block header -# See https://zips.z.cash/protocol/protocol.pdf#blockheader for details +# TODO: check the proposals for every valid time source: CurTime MinTime MaxTime ClampedNow cat "$TEMPLATE_NODE_TEMPLATE_RESPONSE" | \ - $JQ -r '. | "printf 0\(.version)000000\(.previousblockhash)\(.defaultroots.merkleroot)\(.defaultroots.blockcommitmentshash)%x\(.bits)%064xfd4005%02688x \(.curtime) 0 0"' | \ - sh \ - > "$PROPOSAL_DATA" - -# Transaction list -# See https://developer.bitcoin.org/reference/block_chain.html#serialized-blocks for details -# This encoding only works up to 252 transactions, because it only implements 1 byte compact size -# https://developer.bitcoin.org/reference/transactions.html#compactsize-unsigned-integers -cat "$TEMPLATE_NODE_TEMPLATE_RESPONSE" | \ - $JQ -r '. | "printf %02x\(.coinbasetxn.data)\(.transactions | .[]? | {data} | join("")) \(.transactions | length + 1)"' | \ - sh \ - >> "$PROPOSAL_DATA" + $BLOCK_TEMPLATE_TO_PROPOSAL \ + > "$PROPOSAL_DATA" # TODO: put this in a file if it exceeds the bash variable size limit PROPOSAL_ARG_FULL="{ \"mode\": \"proposal\", \"data\": \"$(cat $PROPOSAL_DATA)\" ${PROPOSAL_ARG:+, $PROPOSAL_ARG} }" From 46b7b2d3e7e8784a7a7684254cf52e6d137e19f0 Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 13 Jan 2023 15:00:46 +1000 Subject: [PATCH 11/32] Apply new hex formatting to commitments and nonces in block proposal tests --- .../types/get_block_template/proposal.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs index e2c50324447..4ae36ccc404 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs @@ -173,10 +173,10 @@ pub fn proposal_block_from_template( version, previous_block_hash, merkle_root, - commitment_bytes: block_commitments_hash.into(), + commitment_bytes: block_commitments_hash.bytes_in_serialized_order().into(), time: time.into(), difficulty_threshold, - nonce: [0; 32], + nonce: [0; 32].into(), solution: Solution::default(), }), transactions, From 64efc67e8be81842c13f398f40930e5ec7c7e8e9 Mon Sep 17 00:00:00 2001 From: teor Date: Mon, 16 Jan 2023 10:46:50 +1000 Subject: [PATCH 12/32] Re-add missing imports from rebase --- .../methods/get_block_template_rpcs/types/get_block_template.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template.rs index 29689ed530e..8672ac1a13c 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template.rs @@ -4,8 +4,10 @@ use zebra_chain::{ amount, block::{ChainHistoryBlockTxAuthCommitmentHash, MAX_BLOCK_BYTES, ZCASH_BLOCK_VERSION}, + parameters::Network, serialization::DateTime32, transaction::VerifiedUnminedTx, + transparent, work::difficulty::{CompactDifficulty, ExpandedDifficulty}, }; use zebra_consensus::MAX_BLOCK_SIGOPS; From 14b80dae09d133bc2d1855f5959b75ba387b972c Mon Sep 17 00:00:00 2001 From: teor Date: Mon, 16 Jan 2023 10:47:06 +1000 Subject: [PATCH 13/32] Re-add missing conversions from rebase --- .../types/get_block_template/proposal.rs | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs index 4ae36ccc404..bff45679038 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs @@ -12,7 +12,8 @@ use zebra_chain::{ use crate::methods::{ get_block_template_rpcs::types::{ - default_roots::DefaultRoots, get_block_template::GetBlockTemplate, + default_roots::DefaultRoots, + get_block_template::{GetBlockTemplate, Response}, }, GetBlockHash, }; @@ -53,6 +54,24 @@ impl From for ProposalResponse { } } +impl From for Response { + fn from(error_response: ProposalRejectReason) -> Self { + Self::ProposalMode(ProposalResponse::from(error_response)) + } +} + +impl From for Response { + fn from(proposal_response: ProposalResponse) -> Self { + Self::ProposalMode(proposal_response) + } +} + +impl From for Response { + fn from(template: GetBlockTemplate) -> Self { + Self::TemplateMode(Box::new(template)) + } +} + /// The source of the time in the block proposal header. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum TimeSource { From 7646a8be01993a9cca102ddb4f6e9ad45151d81f Mon Sep 17 00:00:00 2001 From: teor Date: Mon, 16 Jan 2023 10:48:20 +1000 Subject: [PATCH 14/32] Update to new method name after rebase --- .../types/get_block_template/proposal.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs index bff45679038..c3afe443654 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs @@ -196,7 +196,7 @@ pub fn proposal_block_from_template( time: time.into(), difficulty_threshold, nonce: [0; 32].into(), - solution: Solution::default(), + solution: Solution::for_proposal(), }), transactions, }) From a6ee30831ffd8787d3f3af4bdf722d27ca80fd23 Mon Sep 17 00:00:00 2001 From: teor Date: Mon, 16 Jan 2023 10:49:06 +1000 Subject: [PATCH 15/32] Derive a Default impl --- .../types/get_block_template/proposal.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs index c3afe443654..97b4bf64bb7 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs @@ -73,10 +73,11 @@ impl From for Response { } /// The source of the time in the block proposal header. -#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[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. @@ -119,12 +120,6 @@ impl TimeSource { } } -impl Default for TimeSource { - fn default() -> Self { - TimeSource::CurTime - } -} - impl FromStr for TimeSource { type Err = ParseIntError; From d6b8ac31830ce4b0641f00707038ae123ad1cb9a Mon Sep 17 00:00:00 2001 From: teor Date: Mon, 16 Jan 2023 11:01:28 +1000 Subject: [PATCH 16/32] Restore a comment that was accidentally deleted during the rebase --- .../types/get_block_template/proposal.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs index 97b4bf64bb7..a8a6eb6c486 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs @@ -19,6 +19,8 @@ use crate::methods::{ }; /// Error response to a `getblocktemplate` RPC request in proposal mode. +/// +/// See #[derive(Copy, Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "kebab-case")] pub enum ProposalRejectReason { From 4f7e37b308a83876f555d64040ca13d5168aa8b8 Mon Sep 17 00:00:00 2001 From: teor Date: Mon, 16 Jan 2023 12:00:12 +1000 Subject: [PATCH 17/32] Avoid a clippy::unwrap-in-result --- .../types/get_block_template/proposal.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs index a8a6eb6c486..247788e2b43 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs @@ -134,12 +134,13 @@ impl FromStr for TimeSource { "maxtime" => Ok(MaxTime), "clampednow" => Ok(ClampedNow), "rawnow" => Ok(RawNow), - // "raw"u32 - s if s.strip_prefix("raw").is_some() => { - Ok(Raw(s.strip_prefix("raw").unwrap().parse()?)) - } - // "clamped"u32 or just u32 - _ => Ok(Clamped(s.strip_prefix("clamped").unwrap_or(s).parse()?)), + 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()?)), + }, } } } From ce5f66cfe2da08a2308bcfecea4ec53fd7abc324 Mon Sep 17 00:00:00 2001 From: teor Date: Mon, 16 Jan 2023 12:25:23 +1000 Subject: [PATCH 18/32] Temporarily fix the code for a disabled test --- .../common/get_block_template_rpcs/get_block_template.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/zebrad/tests/common/get_block_template_rpcs/get_block_template.rs b/zebrad/tests/common/get_block_template_rpcs/get_block_template.rs index 819d7116e7b..efb56064e13 100644 --- a/zebrad/tests/common/get_block_template_rpcs/get_block_template.rs +++ b/zebrad/tests/common/get_block_template_rpcs/get_block_template.rs @@ -145,7 +145,11 @@ 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 + 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 From 7752bd9e2c16d371ad308a674c93b3160fd8767a Mon Sep 17 00:00:00 2001 From: teor Date: Mon, 16 Jan 2023 12:31:43 +1000 Subject: [PATCH 19/32] Fix tool build with Docker caches --- zebra-utils/Cargo.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/zebra-utils/Cargo.toml b/zebra-utils/Cargo.toml index cf35a55b2e1..ff4065683b1 100644 --- a/zebra-utils/Cargo.toml +++ b/zebra-utils/Cargo.toml @@ -12,6 +12,8 @@ publish = false [[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] From 68166f58d17180b5df778dec88ae3e11e2d23837 Mon Sep 17 00:00:00 2001 From: teor Date: Mon, 16 Jan 2023 13:25:25 +1000 Subject: [PATCH 20/32] Make clippy happy with the temporary fix --- .../tests/common/get_block_template_rpcs/get_block_template.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/zebrad/tests/common/get_block_template_rpcs/get_block_template.rs b/zebrad/tests/common/get_block_template_rpcs/get_block_template.rs index efb56064e13..74069655993 100644 --- a/zebrad/tests/common/get_block_template_rpcs/get_block_template.rs +++ b/zebrad/tests/common/get_block_template_rpcs/get_block_template.rs @@ -146,6 +146,7 @@ async fn try_validate_block_template(client: &RPCRequestClient) -> Result<()> { tracing::info!("calling getblocktemplate with a block proposal...",); // 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, From cdd20b380f6c09bd2806757960b1b35088151147 Mon Sep 17 00:00:00 2001 From: teor Date: Tue, 17 Jan 2023 15:24:10 +1000 Subject: [PATCH 21/32] Give a pass/fail status for each proposal response --- .../zcash-rpc-block-template-to-proposal | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/zebra-utils/zcash-rpc-block-template-to-proposal b/zebra-utils/zcash-rpc-block-template-to-proposal index 64072f63fcd..818ff3bc48e 100755 --- a/zebra-utils/zcash-rpc-block-template-to-proposal +++ b/zebra-utils/zcash-rpc-block-template-to-proposal @@ -179,7 +179,8 @@ for PORT in $PROPOSAL_RPC_PORTS; do # TODO: try modifying $PROPOSAL_DATA to contain the whole jsonrequestobject, # and then call: # xargs --max-chars=2000200 --arg-file="$PROPOSAL_DATA" time $ZCASH_CLI -rpcport="$PORT" getblocktemplate - time $ZCASH_CLI -rpcport="$PORT" getblocktemplate "$PROPOSAL_ARG_FULL" > "$PROPOSAL_NODE_PROPOSAL_RESPONSE" + time $ZCASH_CLI -rpcport="$PORT" getblocktemplate "$PROPOSAL_ARG_FULL" > "$PROPOSAL_NODE_PROPOSAL_RESPONSE" || \ + echo "$ZCASH_CLI -rpcport=$PORT exited with an error" done echo @@ -187,15 +188,17 @@ echo if [ "$PROPOSAL_RPC_PORT_COUNT" -gt 1 ]; then echo "Proposal response diffs between ports $PROPOSAL_RPC_PORTS:" - $DIFF $PROPOSAL_NODES_PROPOSAL_RESPONSE_LIST \ - && ( \ - echo "getblocktemplate proposal responses were identical"; \ - echo ; \ - echo "Final response from $PROPOSAL_NODE_PROPOSAL_RESPONSE:"; \ - cat "$PROPOSAL_NODE_PROPOSAL_RESPONSE"; \ - ) -else - echo "Proposal response from port $PROPOSAL_RPC_PORTS:" - - cat $PROPOSAL_NODES_PROPOSAL_RESPONSE_LIST + $DIFF $PROPOSAL_NODES_PROPOSAL_RESPONSE_LIST && \ + echo "getblocktemplate proposal responses were identical" fi + +echo + +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" + else + echo "Node said proposal was valid, empty success response in $RESPONSE" + fi +done From 925909a5ef03d72bf7728eda3532161141ac0fb0 Mon Sep 17 00:00:00 2001 From: teor Date: Tue, 17 Jan 2023 15:44:37 +1000 Subject: [PATCH 22/32] Accept zcashd block templates in block-template-to-proposal --- .../bin/block-template-to-proposal/main.rs | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/zebra-utils/src/bin/block-template-to-proposal/main.rs b/zebra-utils/src/bin/block-template-to-proposal/main.rs index af7fb42d35c..b6a0460b0e8 100644 --- a/zebra-utils/src/bin/block-template-to-proposal/main.rs +++ b/zebra-utils/src/bin/block-template-to-proposal/main.rs @@ -7,6 +7,7 @@ use std::io::Read; use color_eyre::eyre::Result; +use serde_json::Value; use structopt::StructOpt; use zebra_chain::serialization::ZcashSerialize; @@ -46,13 +47,24 @@ fn main() -> Result<()> { template }); - // parse json - let template: GetBlockTemplate = serde_json::from_str(&template)?; - eprintln!("{template:?}"); + // parse string to generic json + let mut template: Value = serde_json::from_str(&template)?; + eprintln!("{}", template.to_string_pretty()); + + // remove zcashd keys that are incompatible with Zebra + // + // the longpollid key is in a node-specific format + template + .as_object_mut() + .expect("template must be a JSON object") + .remove("longpollid"); + + // parse 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:?}"); + eprintln!("{proposal:#?}"); let proposal = proposal .zcash_serialize_to_vec() From d29b925c2346232a8854678d302430415c92a0c6 Mon Sep 17 00:00:00 2001 From: teor Date: Tue, 17 Jan 2023 15:47:49 +1000 Subject: [PATCH 23/32] Fix pretty printing --- zebra-utils/src/bin/block-template-to-proposal/main.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/zebra-utils/src/bin/block-template-to-proposal/main.rs b/zebra-utils/src/bin/block-template-to-proposal/main.rs index b6a0460b0e8..d615b448491 100644 --- a/zebra-utils/src/bin/block-template-to-proposal/main.rs +++ b/zebra-utils/src/bin/block-template-to-proposal/main.rs @@ -49,7 +49,10 @@ fn main() -> Result<()> { // parse string to generic json let mut template: Value = serde_json::from_str(&template)?; - eprintln!("{}", template.to_string_pretty()); + eprintln!( + "{}", + serde_json::to_string_pretty(&template).expect("re-serialization never fails") + ); // remove zcashd keys that are incompatible with Zebra // From dd6194c8c0f15f897d262b4b5a7bd1a8831f7962 Mon Sep 17 00:00:00 2001 From: teor Date: Tue, 17 Jan 2023 15:53:57 +1000 Subject: [PATCH 24/32] Zebra expects a longpollid, so give it a dummy value --- .../src/bin/block-template-to-proposal/main.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/zebra-utils/src/bin/block-template-to-proposal/main.rs b/zebra-utils/src/bin/block-template-to-proposal/main.rs index d615b448491..c422f039b1c 100644 --- a/zebra-utils/src/bin/block-template-to-proposal/main.rs +++ b/zebra-utils/src/bin/block-template-to-proposal/main.rs @@ -12,7 +12,8 @@ use structopt::StructOpt; use zebra_chain::serialization::ZcashSerialize; use zebra_rpc::methods::get_block_template_rpcs::{ - get_block_template::proposal_block_from_template, types::get_block_template::GetBlockTemplate, + get_block_template::proposal_block_from_template, + types::{get_block_template::GetBlockTemplate, long_poll::LONG_POLL_ID_LENGTH}, }; use zebra_utils::init_tracing; @@ -54,13 +55,14 @@ fn main() -> Result<()> { serde_json::to_string_pretty(&template).expect("re-serialization never fails") ); - // remove zcashd keys that are incompatible with Zebra + // replace zcashd keys that are incompatible with Zebra // - // the longpollid key is in a node-specific format + // 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 .as_object_mut() - .expect("template must be a JSON object") - .remove("longpollid"); + .expect("template must be a JSON object")["longpollid"] = + "0".repeat(LONG_POLL_ID_LENGTH).into(); // parse json to template type let template: GetBlockTemplate = serde_json::from_value(template)?; From 66c54af758eef4f180da7724e7aef00d6339976e Mon Sep 17 00:00:00 2001 From: teor Date: Tue, 17 Jan 2023 15:59:25 +1000 Subject: [PATCH 25/32] Add "required" fields which Zebra requires --- .../bin/block-template-to-proposal/main.rs | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/zebra-utils/src/bin/block-template-to-proposal/main.rs b/zebra-utils/src/bin/block-template-to-proposal/main.rs index c422f039b1c..58a508e8302 100644 --- a/zebra-utils/src/bin/block-template-to-proposal/main.rs +++ b/zebra-utils/src/bin/block-template-to-proposal/main.rs @@ -55,16 +55,29 @@ fn main() -> Result<()> { 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 - .as_object_mut() - .expect("template must be a JSON object")["longpollid"] = - "0".repeat(LONG_POLL_ID_LENGTH).into(); + template_obj["longpollid"] = "0".repeat(LONG_POLL_ID_LENGTH).into(); - // parse json to template type + // 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(); + } + + // parse the modified json to template type let template: GetBlockTemplate = serde_json::from_value(template)?; // generate proposal according to arguments From 760f54ab8b061c3ac36cfb41120ad1c848f93b60 Mon Sep 17 00:00:00 2001 From: teor Date: Tue, 17 Jan 2023 16:13:23 +1000 Subject: [PATCH 26/32] Use curtime as the dummy maxtime in zcashd templates --- .../types/get_block_template/proposal.rs | 11 +++++++++ .../bin/block-template-to-proposal/main.rs | 24 ++++++++++++++++--- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs index 247788e2b43..8a44f179ca0 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs @@ -120,6 +120,17 @@ impl TimeSource { 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 { diff --git a/zebra-utils/src/bin/block-template-to-proposal/main.rs b/zebra-utils/src/bin/block-template-to-proposal/main.rs index 58a508e8302..54c7f6e116b 100644 --- a/zebra-utils/src/bin/block-template-to-proposal/main.rs +++ b/zebra-utils/src/bin/block-template-to-proposal/main.rs @@ -10,7 +10,7 @@ use color_eyre::eyre::Result; use serde_json::Value; use structopt::StructOpt; -use zebra_chain::serialization::ZcashSerialize; +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}, @@ -60,13 +60,13 @@ fn main() -> Result<()> { .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(); @@ -77,6 +77,24 @@ fn main() -> Result<()> { 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)?; From 5dc6f9629dbdb7df2f8963e8b6a84d779e7aaa00 Mon Sep 17 00:00:00 2001 From: teor Date: Tue, 17 Jan 2023 16:31:06 +1000 Subject: [PATCH 27/32] Support large block proposals by reading proposal data from a file --- .../zcash-rpc-block-template-to-proposal | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/zebra-utils/zcash-rpc-block-template-to-proposal b/zebra-utils/zcash-rpc-block-template-to-proposal index 818ff3bc48e..ee036d7cb67 100755 --- a/zebra-utils/zcash-rpc-block-template-to-proposal +++ b/zebra-utils/zcash-rpc-block-template-to-proposal @@ -150,17 +150,18 @@ PROPOSAL_DATA="$ZCASH_RPC_TMP_DIR/proposal-data.json" echo "Turning the template into block proposal data using $BLOCK_TEMPLATE_TO_PROPOSAL..." +echo -n '{ "mode": "proposal", ' > "$PROPOSAL_DATA" +echo -n '"data": "' >> "$PROPOSAL_DATA" # TODO: check the proposals for every valid time source: CurTime MinTime MaxTime ClampedNow cat "$TEMPLATE_NODE_TEMPLATE_RESPONSE" | \ - $BLOCK_TEMPLATE_TO_PROPOSAL \ - > "$PROPOSAL_DATA" - -# TODO: put this in a file if it exceeds the bash variable size limit -PROPOSAL_ARG_FULL="{ \"mode\": \"proposal\", \"data\": \"$(cat $PROPOSAL_DATA)\" ${PROPOSAL_ARG:+, $PROPOSAL_ARG} }" + $BLOCK_TEMPLATE_TO_PROPOSAL | \ + (tr -d '\r\n' || true) \ + >> "$PROPOSAL_DATA" +echo -n '"' >> "$PROPOSAL_DATA" +echo -n "${PROPOSAL_ARG:+, $PROPOSAL_ARG} }" >> "$PROPOSAL_DATA" echo "Block proposal data is in $PROPOSAL_DATA" -# TODO: disable verbose block data -cat "$PROPOSAL_DATA" +#cat "$PROPOSAL_DATA" echo echo @@ -176,10 +177,10 @@ for PORT in $PROPOSAL_RPC_PORTS; do PROPOSAL_NODE_PROPOSAL_RESPONSE=$PROPOSAL_NODES_PROPOSAL_RESPONSE_BASE.$PORT.json PROPOSAL_NODES_PROPOSAL_RESPONSE_LIST="$PROPOSAL_NODES_PROPOSAL_RESPONSE_LIST $PROPOSAL_NODE_PROPOSAL_RESPONSE" - # TODO: try modifying $PROPOSAL_DATA to contain the whole jsonrequestobject, - # and then call: - # xargs --max-chars=2000200 --arg-file="$PROPOSAL_DATA" time $ZCASH_CLI -rpcport="$PORT" getblocktemplate - time $ZCASH_CLI -rpcport="$PORT" getblocktemplate "$PROPOSAL_ARG_FULL" > "$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 From 9974c2916b0251b3b829b892bce321cdca75c9ad Mon Sep 17 00:00:00 2001 From: teor Date: Tue, 17 Jan 2023 17:00:24 +1000 Subject: [PATCH 28/32] Test all valid time modes for each proposal --- .../zcash-rpc-block-template-to-proposal | 62 ++++++++++--------- 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/zebra-utils/zcash-rpc-block-template-to-proposal b/zebra-utils/zcash-rpc-block-template-to-proposal index ee036d7cb67..7e00e78f778 100755 --- a/zebra-utils/zcash-rpc-block-template-to-proposal +++ b/zebra-utils/zcash-rpc-block-template-to-proposal @@ -16,10 +16,11 @@ function 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: +# Override the commands and settings 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_SOURCES="${TIME_SOURCES:-CurTime MinTime MaxTime ClampedNow}" # Process arguments @@ -32,10 +33,8 @@ TEMPLATE_RPC_PORT=$1 shift PROPOSAL_RPC_PORTS="" -PROPOSAL_RPC_PORT_COUNT=0 while [ -n "${1:-}" ] && [ "${1-}" != "--" ]; do PROPOSAL_RPC_PORTS="$PROPOSAL_RPC_PORTS $1" - PROPOSAL_RPC_PORT_COUNT=$((PROPOSAL_RPC_PORT_COUNT + 1)) shift done @@ -146,22 +145,25 @@ echo "Block template data is in $TEMPLATE_NODE_TEMPLATE_RESPONSE" echo -PROPOSAL_DATA="$ZCASH_RPC_TMP_DIR/proposal-data.json" +PROPOSAL_DATA_BASE="$ZCASH_RPC_TMP_DIR/proposal-data" echo "Turning the template into block proposal data using $BLOCK_TEMPLATE_TO_PROPOSAL..." -echo -n '{ "mode": "proposal", ' > "$PROPOSAL_DATA" -echo -n '"data": "' >> "$PROPOSAL_DATA" -# TODO: check the proposals for every valid time source: CurTime MinTime MaxTime ClampedNow -cat "$TEMPLATE_NODE_TEMPLATE_RESPONSE" | \ - $BLOCK_TEMPLATE_TO_PROPOSAL | \ - (tr -d '\r\n' || true) \ - >> "$PROPOSAL_DATA" -echo -n '"' >> "$PROPOSAL_DATA" -echo -n "${PROPOSAL_ARG:+, $PROPOSAL_ARG} }" >> "$PROPOSAL_DATA" +for TIME_SOURCE in $TIME_SOURCES; do + PROPOSAL_DATA="$PROPOSAL_DATA_BASE.$TIME_SOURCE.json" + + echo -n '{ "mode": "proposal", ' > "$PROPOSAL_DATA" + echo -n '"data": "' >> "$PROPOSAL_DATA" + cat "$TEMPLATE_NODE_TEMPLATE_RESPONSE" | \ + $BLOCK_TEMPLATE_TO_PROPOSAL | \ + (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" -#cat "$PROPOSAL_DATA" +echo "Block proposal data is in $PROPOSAL_DATA_BASE*" +#cat "$PROPOSAL_DATA_BASE"* echo echo @@ -173,25 +175,27 @@ echo PROPOSAL_NODES_PROPOSAL_RESPONSE_BASE="$ZCASH_RPC_TMP_DIR/proposal-check-getblocktemplate-proposal" PROPOSAL_NODES_PROPOSAL_RESPONSE_LIST="" -for PORT in $PROPOSAL_RPC_PORTS; do - PROPOSAL_NODE_PROPOSAL_RESPONSE=$PROPOSAL_NODES_PROPOSAL_RESPONSE_BASE.$PORT.json - 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" +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 -if [ "$PROPOSAL_RPC_PORT_COUNT" -gt 1 ]; then - echo "Proposal response diffs between ports $PROPOSAL_RPC_PORTS:" +echo "Proposal response diffs between ports $PROPOSAL_RPC_PORTS and time sources $TIME_SOURCES:" - $DIFF $PROPOSAL_NODES_PROPOSAL_RESPONSE_LIST && \ - echo "getblocktemplate proposal responses were identical" -fi +$DIFF --from-file=$PROPOSAL_NODES_PROPOSAL_RESPONSE_LIST && \ + echo "getblocktemplate proposal responses were identical" echo From 51d44dc3d00764febb3f065bb8fcdfc122a3b02f Mon Sep 17 00:00:00 2001 From: teor Date: Tue, 17 Jan 2023 17:07:17 +1000 Subject: [PATCH 29/32] Allow the user to set the time command --- zebra-utils/zcash-rpc-block-template-to-proposal | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/zebra-utils/zcash-rpc-block-template-to-proposal b/zebra-utils/zcash-rpc-block-template-to-proposal index 7e00e78f778..17b5f1c62db 100755 --- a/zebra-utils/zcash-rpc-block-template-to-proposal +++ b/zebra-utils/zcash-rpc-block-template-to-proposal @@ -18,6 +18,7 @@ function usage() # Override the commands and settings used by this script using these environmental variables: ZCASH_CLI="${ZCASH_CLI:-zcash-cli}" +TIME="time" DIFF="${DIFF:-diff --unified --color=always}" BLOCK_TEMPLATE_TO_PROPOSAL="${BLOCK_TEMPLATE_TO_PROPOSAL:-block-template-to-proposal}" TIME_SOURCES="${TIME_SOURCES:-CurTime MinTime MaxTime ClampedNow}" @@ -138,7 +139,7 @@ echo "getblocktemplate $TEMPLATE_ARG_FULL" echo echo "Querying $TEMPLATE_NODE $TEMPLATE_NET chain at height >=$TEMPLATE_HEIGHT..." -time $ZCASH_CLI -rpcport="$TEMPLATE_RPC_PORT" getblocktemplate "$TEMPLATE_ARG_FULL" > "$TEMPLATE_NODE_TEMPLATE_RESPONSE" +$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" @@ -184,7 +185,7 @@ for TIME_SOURCE in $TIME_SOURCES; do # read the proposal data from a file, to avoid command-line length limits cat "$PROPOSAL_DATA" | \ - time $ZCASH_CLI -rpcport="$PORT" -stdin getblocktemplate \ + $TIME $ZCASH_CLI -rpcport="$PORT" -stdin getblocktemplate \ > "$PROPOSAL_NODE_PROPOSAL_RESPONSE" || \ echo "$ZCASH_CLI -rpcport=$PORT exited with an error" done From 21f64df393eba8aba430f37d1ae67369ffe37a23 Mon Sep 17 00:00:00 2001 From: teor Date: Tue, 17 Jan 2023 17:07:37 +1000 Subject: [PATCH 30/32] Put debug logs into their own files --- zebra-utils/zcash-rpc-block-template-to-proposal | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/zebra-utils/zcash-rpc-block-template-to-proposal b/zebra-utils/zcash-rpc-block-template-to-proposal index 17b5f1c62db..63defbb9a0b 100755 --- a/zebra-utils/zcash-rpc-block-template-to-proposal +++ b/zebra-utils/zcash-rpc-block-template-to-proposal @@ -152,11 +152,13 @@ echo "Turning the template into block proposal data using $BLOCK_TEMPLATE_TO_PRO 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 | \ + $BLOCK_TEMPLATE_TO_PROPOSAL \ + 2> "$PROPOSAL_DEBUG" | \ (tr -d '\r\n' || true) \ >> "$PROPOSAL_DATA" echo -n '"' >> "$PROPOSAL_DATA" From 252c97b78a826f71107bd68eb768df85caeb26d5 Mon Sep 17 00:00:00 2001 From: teor Date: Tue, 17 Jan 2023 17:55:08 +1000 Subject: [PATCH 31/32] Exit with an error status when any proposal is invalid --- zebra-utils/zcash-rpc-block-template-to-proposal | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/zebra-utils/zcash-rpc-block-template-to-proposal b/zebra-utils/zcash-rpc-block-template-to-proposal index 63defbb9a0b..483f0fe7770 100755 --- a/zebra-utils/zcash-rpc-block-template-to-proposal +++ b/zebra-utils/zcash-rpc-block-template-to-proposal @@ -202,11 +202,15 @@ $DIFF --from-file=$PROPOSAL_NODES_PROPOSAL_RESPONSE_LIST && \ 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 + +exit $EXIT_STATUS From a295faec52eb9196f5191761826e8fb3faa8afa3 Mon Sep 17 00:00:00 2001 From: teor Date: Tue, 17 Jan 2023 17:58:31 +1000 Subject: [PATCH 32/32] Log the date and time to make it easier to match errors to node logs --- zebra-utils/zcash-rpc-block-template-to-proposal | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/zebra-utils/zcash-rpc-block-template-to-proposal b/zebra-utils/zcash-rpc-block-template-to-proposal index 483f0fe7770..2c5e8a08b39 100755 --- a/zebra-utils/zcash-rpc-block-template-to-proposal +++ b/zebra-utils/zcash-rpc-block-template-to-proposal @@ -16,11 +16,16 @@ function 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 and settings used by this script using these environmental variables: +# Override the commands used by this script using these environmental variables: ZCASH_CLI="${ZCASH_CLI:-zcash-cli}" -TIME="time" 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 @@ -62,6 +67,8 @@ if [ $# -ge 1 ]; then 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) @@ -139,6 +146,7 @@ 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" @@ -173,6 +181,7 @@ 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" @@ -213,4 +222,6 @@ for RESPONSE in $PROPOSAL_NODES_PROPOSAL_RESPONSE_LIST; do fi done +$DATE + exit $EXIT_STATUS