#!/usr/bin/env bash set -euo pipefail # Sends a `zcash-cli` command to a Zebra and zcashd instance, # and compares the results. # # Uses the configured `zcash-cli` RPC port, # and the `zebrad` port supplied on the command-line. function usage() { echo "Usage:" echo "$0 zebra-rpc-port rpc-name [rpc-args... ]" } # 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}" # Zcashd authentication modes: # - Use `-rpccookiefile=your/cookie/file` for a cookie file. # - Use `-rpcpassword=your-password` for a password. ZCASHD_EXTRA_ARGS="${ZCASHD_EXTRA_ARGS:-}" if [ $# -lt 2 ]; then usage exit 1 fi ZEBRAD_RPC_PORT=$1 shift # 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=.rpc-diff -d 2>/dev/null || mktemp -d) ZEBRAD_RELEASE_INFO="$ZCASH_RPC_TMP_DIR/first-check-getinfo.json" ZCASHD_RELEASE_INFO="$ZCASH_RPC_TMP_DIR/second-check-getinfo.json" echo "Checking first node release info..." $ZCASH_CLI -rpcport="$ZEBRAD_RPC_PORT" getinfo > "$ZEBRAD_RELEASE_INFO" ZEBRAD=$(cat "$ZEBRAD_RELEASE_INFO" | grep '"subversion"' | cut -d: -f2 | cut -d/ -f2 | \ tr 'A-Z' 'a-z' | sed 's/magicbean/zcashd/ ; s/zebra$/zebrad/') echo "Checking second node release info..." $ZCASH_CLI $ZCASHD_EXTRA_ARGS getinfo > "$ZCASHD_RELEASE_INFO" ZCASHD=$(cat "$ZCASHD_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 $ZEBRAD (port $ZEBRAD_RPC_PORT) and $ZCASHD ($ZCASH_CLI zcash.conf port)." echo ZEBRAD_BLOCKCHAIN_INFO="$ZCASH_RPC_TMP_DIR/$ZEBRAD-check-getblockchaininfo.json" ZCASHD_BLOCKCHAIN_INFO="$ZCASH_RPC_TMP_DIR/$ZCASHD-check-getblockchaininfo.json" echo "Checking $ZEBRAD network and tip height..." $ZCASH_CLI -rpcport="$ZEBRAD_RPC_PORT" getblockchaininfo > "$ZEBRAD_BLOCKCHAIN_INFO" ZEBRAD_NET=$(cat "$ZEBRAD_BLOCKCHAIN_INFO" | grep '"chain"' | cut -d: -f2 | tr -d ' ,"') ZEBRAD_HEIGHT=$(cat "$ZEBRAD_BLOCKCHAIN_INFO" | grep '"blocks"' | cut -d: -f2 | tr -d ' ,"') echo "Checking $ZCASHD network and tip height..." $ZCASH_CLI $ZCASHD_EXTRA_ARGS getblockchaininfo > "$ZCASHD_BLOCKCHAIN_INFO" ZCASHD_NET=$(cat "$ZCASHD_BLOCKCHAIN_INFO" | grep '"chain"' | cut -d: -f2 | tr -d ' ,"') ZCASHD_HEIGHT=$(cat "$ZCASHD_BLOCKCHAIN_INFO" | grep '"blocks"' | cut -d: -f2 | tr -d ' ,"') echo if [ "$ZEBRAD_NET" != "$ZCASHD_NET" ]; then echo "WARNING: comparing RPC responses from different networks:" echo "$ZEBRAD is on: $ZEBRAD_NET" echo "$ZCASHD is on: $ZCASHD_NET" echo fi if [ "$ZEBRAD_HEIGHT" -ne "$ZCASHD_HEIGHT" ]; then echo "WARNING: comparing RPC responses from different heights:" echo "$ZEBRAD is at: $ZEBRAD_HEIGHT" echo "$ZCASHD is at: $ZCASHD_HEIGHT" echo fi ZEBRAD_RESPONSE="$ZCASH_RPC_TMP_DIR/$ZEBRAD-$ZEBRAD_NET-$ZEBRAD_HEIGHT-$1.json" ZCASHD_RESPONSE="$ZCASH_RPC_TMP_DIR/$ZCASHD-$ZCASHD_NET-$ZCASHD_HEIGHT-$1.json" echo "Request:" echo "$@" echo echo "Querying $ZEBRAD $ZEBRAD_NET chain at height >=$ZEBRAD_HEIGHT..." time $ZCASH_CLI -rpcport="$ZEBRAD_RPC_PORT" "$@" > "$ZEBRAD_RESPONSE" echo echo "Querying $ZCASHD $ZCASHD_NET chain at height >=$ZCASHD_HEIGHT..." time $ZCASH_CLI $ZCASHD_EXTRA_ARGS "$@" > "$ZCASHD_RESPONSE" echo echo echo "Response diff between $ZEBRAD and $ZCASHD:" $DIFF "$ZEBRAD_RESPONSE" "$ZCASHD_RESPONSE" \ && ( \ echo "RPC responses were identical"; \ echo ; \ echo "$ZEBRAD_RESPONSE:"; \ cat "$ZEBRAD_RESPONSE"; \ ) EXIT_STATUS=$? # Consistency checks between RPCs # # TODO: # - sum of getaddressutxos.satoshis equals getaddressbalance # - set of getaddressutxos.txid is a subset of getaddresstxids 1 # - getblockchaininfo.bestblockhash equals getbestblockhash if [ "$1" == "getaddressutxos" ]; then set "getaddressbalance" "$2" elif [ "$1" == "getrawmempool" ]; then # Call `getrawmempool` again as a dummy request (this script isn't set up to do multiple cross-check calls) set "getrawmempool" else exit $EXIT_STATUS fi ZEBRAD_CHECK_RESPONSE="$ZCASH_RPC_TMP_DIR/$ZEBRAD-$ZEBRAD_NET-$ZEBRAD_HEIGHT-$1.json" ZCASHD_CHECK_RESPONSE="$ZCASH_RPC_TMP_DIR/$ZCASHD-$ZCASHD_NET-$ZCASHD_HEIGHT-$1.json" echo echo "Cross-checking request:" echo "$@" echo echo "Querying $ZEBRAD $ZEBRAD_NET chain at height >=$ZEBRAD_HEIGHT..." $ZCASH_CLI -rpcport="$ZEBRAD_RPC_PORT" "$@" > "$ZEBRAD_CHECK_RESPONSE" echo "Querying $ZCASHD $ZCASHD_NET chain at height >=$ZCASHD_HEIGHT..." $ZCASH_CLI $ZCASHD_EXTRA_ARGS "$@" > "$ZCASHD_CHECK_RESPONSE" echo echo "$1 diff between $ZEBRAD and $ZCASHD:" $DIFF "$ZEBRAD_CHECK_RESPONSE" "$ZCASHD_CHECK_RESPONSE" \ && ( \ echo "RPC check responses were identical"; \ echo ; \ echo "$ZEBRAD_CHECK_RESPONSE:"; \ cat "$ZEBRAD_CHECK_RESPONSE"; \ ) CHECK_EXIT_STATUS=$? if [ "$1" == "getaddressbalance" ]; then echo echo "Extracting getaddressbalance.balance..." ZEBRAD_NUM_RESPONSE="$ZCASH_RPC_TMP_DIR/$ZEBRAD-$ZEBRAD_NET-$ZEBRAD_HEIGHT-getaddressbalance-num.txt" ZCASHD_NUM_RESPONSE="$ZCASH_RPC_TMP_DIR/$ZCASHD-$ZCASHD_NET-$ZCASHD_HEIGHT-getaddressbalance-num.txt" cat "$ZEBRAD_CHECK_RESPONSE" | $JQ '.balance' > "$ZEBRAD_NUM_RESPONSE" cat "$ZCASHD_CHECK_RESPONSE" | $JQ '.balance' > "$ZCASHD_NUM_RESPONSE" echo "Summing getaddressutxos.satoshis..." ZEBRAD_SUM_RESPONSE="$ZCASH_RPC_TMP_DIR/$ZEBRAD-$ZEBRAD_NET-$ZEBRAD_HEIGHT-getaddressutxos-sum.txt" ZCASHD_SUM_RESPONSE="$ZCASH_RPC_TMP_DIR/$ZCASHD-$ZCASHD_NET-$ZCASHD_HEIGHT-getaddressutxos-sum.txt" cat "$ZEBRAD_RESPONSE" | $JQ 'map(.satoshis) | add // 0' > "$ZEBRAD_SUM_RESPONSE" cat "$ZCASHD_RESPONSE" | $JQ 'map(.satoshis) | add // 0' > "$ZCASHD_SUM_RESPONSE" echo echo "Balance diff between $ZEBRAD and $ZCASHD:" echo "(for both getaddressbalance and getaddressutxos)" $DIFF --from-file="$ZEBRAD_NUM_RESPONSE" "$ZCASHD_NUM_RESPONSE" \ "$ZEBRAD_SUM_RESPONSE" "$ZCASHD_SUM_RESPONSE" \ && ( \ echo "RPC balances were identical"; \ echo ; \ echo "$ZEBRAD_NUM_RESPONSE:"; \ cat "$ZEBRAD_NUM_RESPONSE"; \ ) COMPARE_EXIT_STATUS=$? if [ $COMPARE_EXIT_STATUS -ne 0 ]; then exit $COMPARE_EXIT_STATUS fi fi if [ "$1" == "getrawmempool" ] && [ $CHECK_EXIT_STATUS != 0 ]; then set TRANSACTION_ID set TRANSACTION_HEX_FILE set TRANSACTION_DECODED ZEBRAD_TRANSACTION_IDS=$(cat "$ZEBRAD_RESPONSE" | $JQ -r 'join(" ")') ZCASHD_TRANSACTION_IDS=$(cat "$ZCASHD_RESPONSE" | $JQ -r 'join(" ")') echo echo "# Dumping transactions from zebrad mempool" echo for TRANSACTION_ID in $ZEBRAD_TRANSACTION_IDS; do TRANSACTION_HEX_FILE="$ZCASH_RPC_TMP_DIR/$ZEBRAD-$ZEBRAD_NET-$ZEBRAD_HEIGHT-$TRANSACTION_ID.json" $ZCASH_CLI -rpcport="$ZEBRAD_RPC_PORT" getrawtransaction $TRANSACTION_ID 0 > $TRANSACTION_HEX_FILE echo "## Displaying transaction $TRANSACTION_ID from zebrad" echo # read the proposal data from a file, to avoid command-line length limits TRANSACTION_DECODED=`cat "$TRANSACTION_HEX_FILE" | \ $ZCASH_CLI $ZCASHD_EXTRA_ARGS -stdin decoderawtransaction` echo $TRANSACTION_DECODED | $JQ echo done echo echo "# Dumping transactions from zcashd mempool" echo for TRANSACTION_ID in $ZCASHD_TRANSACTION_IDS; do TRANSACTION_HEX_FILE="$ZCASH_RPC_TMP_DIR/$ZCASHD-$ZCASHD_NET-$ZCASHD_HEIGHT-TRANSACTION_HEX-$TRANSACTION_ID.json" $ZCASH_CLI $ZCASHD_EXTRA_ARGS getrawtransaction $TRANSACTION_ID 0 > $TRANSACTION_HEX_FILE echo "## Displaying transaction $TRANSACTION_ID from zcashd" echo # read the proposal data from a file, to avoid command-line length limits TRANSACTION_DECODED=`cat "$TRANSACTION_HEX_FILE" | \ $ZCASH_CLI $ZCASHD_EXTRA_ARGS -stdin decoderawtransaction` echo $TRANSACTION_DECODED | $JQ echo done fi echo "Full RPC output is in $ZCASH_RPC_TMP_DIR" if [ $EXIT_STATUS -ne 0 ]; then exit $EXIT_STATUS else exit $CHECK_EXIT_STATUS fi