#!/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
