add rudimentary network testing tools, which helped reproduce and diagnose several networking bugs that have been plaguing production krypton

This commit is contained in:
Jude Nelson
2020-10-17 03:28:10 -04:00
parent 51a410ac6f
commit 119cc47421
11 changed files with 1168 additions and 0 deletions

15
net-test/README.md Normal file
View File

@@ -0,0 +1,15 @@
# Stacks Network Testing
A rudimentary set of tools for testing multiple Stacks nodes at once, locally.
Relevant files:
* `bin/start.sh` -- start up a master node, a miner node, or a follower node, as
well as ancilliary processes.
* `bin/faucet.sh` -- a rudimentary Bitcoin faucet.
* `bin/txload.sh` -- a rudimentary Stacks transaction generator and load-tester.
* `etc/*.in` -- templates that `bin/start.sh` uses to create configuration
files.
To use, you will need to install `stacks-node`, `blockstack-cli`,
`bitcoin-neon-controller`, and `bin/faucet.sh` to somewhere in your `$PATH`.
You will also need a recent `bitcoind` and `bitcoin-cli`.

26
net-test/bin/config.sh Normal file
View File

@@ -0,0 +1,26 @@
# local config
__ROOT="$(realpath "$(pwd)"/..)"
__ETC="$_ROOT/etc"
__MNT="$_ROOT/mnt"
BITCOIN_CONF="$__ETC/bitcoin.conf"
BITCOIN_CONTROLLER_CONF="$__ETC/bitcoin-neon-controller.toml"
STACKS_MASTER_CONF="$__ETC/stacks-master.toml"
STACKS_MINER_CONF="$__ETC/stacks-miner.toml"
STACKS_FOLLOWER_CONF="$__ETC/stacks-follower.toml"
BITCOIN_LOGFILE="$__MNT/bitcoin.log"
BITCOIN_NEON_CONTROLLER_LOGFILE="$__MNT/bitcoin-neon-controller.log"
STACKS_MASTER_LOGFILE="$__MNT/stacks-node-master.log"
STACKS_MINER_LOGFILE="$__MNT/stacks-node-miner.log"
STACKS_FOLLOWER_LOGFILE="$__MNT/stacks-node-follower.log"
FAUCET_LOGFILE="$__MNT/faucet.log"
STACKS_MASTER_PUBLIC_IP="127.0.0.1"
__NOW="$(date +%s)"
STACKS_CHAINSTATE_DIR="$__MNT/stacks-chainstate-$__NOW"
BITCOIN_DATA_DIR="$__MNT/bitcoin-$__NOW"
echo >&2 "Loaded external config"

294
net-test/bin/faucet.sh Executable file
View File

@@ -0,0 +1,294 @@
#!/bin/bash
# Yup, it's a faucet HTTP server written in bash. This is what my life has come to.
MAX_BODY_LENGTH=65536
FAUCET_AMOUNT="1.0"
MODE="$1"
BITCOIN_CONF="$2"
set -uo pipefail
exit_error() {
printf >&2 "$1"
exit 1
}
log() {
printf >&2 "%s\n" "$1"
}
http_ok() {
local CONTENT_LENGTH
local CONTENT_TYPE
CONTENT_LENGTH=$1
CONTENT_TYPE=$2
printf "HTTP/1.0 200 OK\r\nContent-Length: $CONTENT_LENGTH\r\nContent-Type: $CONTENT_TYPE\r\n\r\n"
}
http_401() {
printf "HTTP/1.0 401 Unsupported Method\r\n\r\n"
}
http_500() {
local ERR="$1"
local ERR_LEN=${#ERR}
printf "HTTP/1.0 500 Internal Server error\r\nContent-Length: $ERR_LEN\r\nContent-Type: text/plain\r\n\r\n$ERR"
}
http_404() {
printf "HTTP/1.0 404 Not Found\r\n\r\n"
}
get_ping() {
http_ok 5 "text/plain"
printf "alive"
return 0
}
get_bitcoin_ping() {
bitcoin-cli -conf="$BITCOIN_CONF" ping >/dev/null 2>&1
if [ $? -eq 0 ]; then
local MSG="Bitcoind appears to be running"
http_ok ${#MSG} "text/plain"
echo "$MSG"
return 0
else
http_500 "Bitcoind appears to be stopped"
return 1
fi
}
get_balance() {
BALANCE="$(bitcoin-cli -conf="$BITCOIN_CONF" getbalance 2>&1)"
if [ $? -eq 0 ]; then
http_ok "${#BALANCE}" "text/plain"
echo "$BALANCE"
return 0
else
http_500 "$BALANCE"
return 1
fi
}
get_utxos() {
local ADDR="$1"
UTXOS="$(bitcoin-cli -conf="$BITCOIN_CONF" listunspent 1 1000000 "[\"$ADDR\"]" 2>&1)"
if [ $? -eq 0 ]; then
http_ok ${#UTXOS} "application/json"
echo "$UTXOS"
return 0
else
http_500 "$UTXOS"
return 1
fi
}
get_confirmations() {
local TXID="$1"
CONFIRMATIONS="$(bitcoin-cli -conf="$BITCOIN_CONF" gettransaction "$TXID" | jq -r '.confirmations')"
RC=$?
if [ $RC -eq 0 ]; then
http_ok ${#CONFIRMATIONS} "text/plain"
echo "$CONFIRMATIONS"
return 0
elif [ $RC -eq 1 ]; then
http_500 "$CONFIRMATIONS"
return 1
else
http_404
return 2
fi
}
post_sendbtc() {
local ADDR
# format: address\n
read ADDR
TXID="$(bitcoin-cli -conf="$BITCOIN_CONF" sendtoaddress "$ADDR" "$FAUCET_AMOUNT" 2>&1)"
if [ $? -ne 0 ]; then
http_500 "$TXID"
return 1
fi
ERR="$(bitcoin-cli -conf="$BITCOIN_CONF" importaddress "$ADDR" 2>&1)"
if [ $? -ne 0 ]; then
http_500 "$ERR"
return 1
fi
http_ok ${#TXID} "text/plain"
echo "$TXID"
return 0
}
parse_request() {
local REQLINE
local VERB=""
local REQPATH=""
local CONTENT_TYPE=""
local CONTENT_LENGTH=0
while read REQLINE; do
# trim trailing whitespace
REQLINE="${REQLINE%"${REQLINE##*[![:space:]]}"}"
if [ -z "$REQLINE" ]; then
break
fi
# log " reqline = '$REQLINE'"
TOK="$(echo "$REQLINE" | egrep "GET|POST" | sed -r 's/^(GET|POST)[ ]+([^ ]+)[ ]+HTTP\/1.(0|1)$/\1 \2/g')"
if [ -n "$TOK" ] && [ -z "$VERB" ] && [ -z "$REQPATH" ]; then
set -- $TOK
VERB="$1"
REQPATH="$2"
continue
fi
TOK="$(echo "$REQLINE" | grep -i "content-type" | cut -d ' ' -f 2)"
if [ -n "$TOK" ] && [ -z "$CONTENT_TYPE" ]; then
CONTENT_TYPE="${TOK,,}"
continue
fi
TOK="$(echo "$REQLINE" | grep -i "content-length" | cut -d ' ' -f 2)"
if [ -n "$TOK" ] && [ $CONTENT_LENGTH -eq 0 ]; then
if [[ "$TOK" =~ ^[0-9]+$ ]]; then
CONTENT_LENGTH="$TOK"
continue
fi
fi
done
if [ $CONTENT_LENGTH -gt $MAX_BODY_LENGTH ]; then
exit 1
fi
if [ -z "$VERB" ] || [ -z "$REQPATH" ]; then
exit 1
fi
# log " verb = '$VERB', reqpath = '$REQPATH', content-type = '$CONTENT_TYPE', content-length = '$CONTENT_LENGTH'"
printf "$VERB\n$REQPATH\n$CONTENT_TYPE\n$CONTENT_LENGTH\n"
dd bs=$CONTENT_LENGTH 2>/dev/null
return 0
}
handle_request() {
local VERB
local REQPATH
local CONTENT_TYPE
local CONTENT_LENGTH
local STATUS=200
read VERB
read REQPATH
read CONTENT_TYPE
read CONTENT_LENGTH
case "$VERB" in
GET)
case "$REQPATH" in
/ping)
get_ping
if [ $? -ne 0 ]; then
STATUS=500
fi
;;
/bitcoin)
get_bitcoin_ping
if [ $? -ne 0 ]; then
STATUS=500
fi
;;
/balance)
get_balance
if [ $? -ne 0 ]; then
STATUS=500
fi
;;
/confirmations/*)
TXID="${REQPATH#/confirmations/}"
get_confirmations "$TXID"
if [ $? -eq 1 ]; then
STATUS=500
elif [ $? -eq 2 ]; then
STATUS=404
fi
;;
/utxos/*)
ADDR="${REQPATH#/utxos/}"
get_utxos "$ADDR"
if [ $? -ne 0 ]; then
STATUS=500
fi
;;
*)
http_404
STATUS=404
;;
esac
;;
POST)
case "$REQPATH" in
/fund)
if [ "$CONTENT_TYPE" != "text/plain" ]; then
http_401
STATUS=401
else
post_sendbtc
if [ $? -ne 0 ]; then
STATUS=500
fi
fi
;;
*)
http_404
STATUS=404
;;
esac
;;
*)
http_401
STATUS=404
;;
esac
log "[$(date +%s)] $VERB $REQPATH ($CONTENT_LENGTH bytes) - $STATUS"
}
usage() {
exit_error "Usage:\n $0 serve </path/to/bitcoin.conf>\n $0 <port> </path/to/bitcoin.conf>\n"
}
if [ -z "$MODE" ] || [ -z "$BITCOIN_CONF" ]; then
usage
fi
if [ "$MODE" = "serve" ]; then
parse_request | handle_request
exit 0
elif [ "$MODE" = "parse" ]; then
# undocumented test mode
parse_request
exit 0
fi
if ! [[ $MODE =~ ^[0-9]+$ ]]; then
usage
fi
for cmd in ncat bitcoin-cli egrep grep tr dd sed cut date; do
which $cmd >/dev/null 2>&1 || exit_error "Missing command: $cmd"
done
exec ncat -k -l -p $MODE -c "bash $0 serve \"$BITCOIN_CONF\""

363
net-test/bin/start.sh Executable file
View File

@@ -0,0 +1,363 @@
#!/bin/bash
MODE="$1"
set -uo pipefail
BITCOIN_LOGFILE="/mnt/bitcoin.log"
BITCOIN_NEON_CONTROLLER_LOGFILE="/mnt/bitcoin-neon-controller.log"
STACKS_MASTER_LOGFILE="/mnt/stacks-node-master.log"
STACKS_MINER_LOGFILE="/mnt/stacks-node-miner.log"
STACKS_FOLLOWER_LOGFILE="/mnt/stacks-node-follower.log"
FAUCET_LOGFILE="/mnt/faucet.log"
BITCOIN_CONF="/etc/bitcoin.conf"
BITCOIN_CONTROLLER_CONF="/etc/bitcoin-neon-controller.toml"
STACKS_MASTER_CONF="/etc/stacks-master.toml"
STACKS_MINER_CONF="/etc/stacks-miner.toml"
STACKS_FOLLOWER_CONF="/etc/stacks-follower.toml"
STACKS_CHAINSTATE_DIR="/mnt/stacks-chainstate"
BITCOIN_DATA_DIR="/mnt/bitcoin"
STACKS_MASTER_PUBLIC_IP="127.0.0.1"
FAUCET_PORT=8080
if [ -f "./config.sh" ]; then
source ./config.sh
fi
function exit_error() {
printf "$1" >&2
exit 1
}
function log() {
printf "%s" "$1" >&2
}
function logln() {
printf "%s\n" "$1" >&2
}
start_bitcoind() {
logln "Setting up Bitcoind..."
test -f "$BITCOIN_CONF".in || exit_error "No such file or directory: $BITCOIN_CONF"
mkdir -p "$BITCOIN_DATA_DIR" || exit_error "Failed to create Bitcoin data directory $BITCOIN_DATA_DIR"
log " Generating Bitcoind config file..."
sed -r \
-e "s!@@BITCOIN_DATA_DIR@@!$BITCOIN_DATA_DIR!g" \
"$BITCOIN_CONF".in \
> "$BITCOIN_CONF"
logln "ok"
log "Starting bitcoind..."
bitcoind -conf="$BITCOIN_CONF" >"$BITCOIN_LOGFILE" 2>&1 &
local BITCOIN_PID=$!
logln "PID $BITCOIN_PID"
while true; do
bitcoin-cli -regtest -conf="$BITCOIN_CONF" ping >/dev/null 2>&1
if [ $? -eq 0 ]; then
break
fi
sleep 1
done
echo "$BITCOIN_PID"
return 0
}
start_bitcoind_controller() {
logln "Setting up Bitcoind controller..."
test -f "$BITCOIN_CONTROLLER_CONF".in || exit_error "No such file or directory: $BITCOIN_CONTROLLER_CONF.in"
log " Generating Bitcoind controller config file..."
local NOW="$(date +%s)"
sed -r \
-e "s/@@BITCOIN_NEON_CONTROLLER_GENESIS_TIMESTAMP@@/$NOW/g" \
"$BITCOIN_CONTROLLER_CONF".in \
> "$BITCOIN_CONTROLLER_CONF"
logln "ok"
log "Starting bitcoind controller..."
bitcoin-neon-controller "$BITCOIN_CONTROLLER_CONF" >"$BITCOIN_NEON_CONTROLLER_LOGFILE" 2>&1 &
local RC=$?
local BITCOIN_NEON_CONTROLLER_PID=$!
if [ $RC -ne 0 ]; then
logln "FAILED"
return 1
fi
logln "PID $BITCOIN_NEON_CONTROLLER_PID"
echo "$BITCOIN_NEON_CONTROLLER_PID"
return 0
}
start_faucet() {
logln "Setting up Bitcoin faucet..."
test -f "$BITCOIN_CONTROLLER_CONF" || exit_error "No such file or directory: $BITCOIN_CONTROLLER_CONF"
test -f "$BITCOIN_CONF" || exit_error "No such file or directory: $BITCOIN_CONF"
local PRIVKEY="$(cat "$BITCOIN_CONTROLLER_CONF" | grep "# WIF" | cut -d ' ' -f 3)"
log " Importing faucet private key..."
bitcoin-cli -conf="$BITCOIN_CONF" importprivkey "$PRIVKEY" >"$FAUCET_LOGFILE" 2>&1
local RC=$?
if [ $RC -ne 0 ]; then
logln "FAILED!"
return 1
fi
logln "ok"
local MINER_ADDR="$(cat "$BITCOIN_CONTROLLER_CONF" | grep "miner_address =" | cut -d ' ' -f 3 | sed -r 's/"//g')"
log " Importing miner address $MINER_ADDR..."
echo "Try importing miner address $MINER_ADDR" >>"$FAUCET_LOGFILE"
bitcoin-cli -conf="$BITCOIN_CONF" importaddress "$MINER_ADDR" >>"$FAUCET_LOGFILE" 2>&1
RC=$?
if [ $RC -ne 0 ]; then
logln "FAILED!"
return 1
fi
logln "ok"
log "Starting Bitcoin faucet..."
faucet.sh "$FAUCET_PORT" "$BITCOIN_CONF" >"$FAUCET_LOGFILE" 2>&1 &
local FAUCET_PID=$!
logln "PID $FAUCET_PID"
echo "$FAUCET_PID"
return 0
}
start_stacks_master_node() {
logln "Setting up Stacks master node..."
test -f "$STACKS_MASTER_CONF".in || exit_error "No such file or directory: $STACKS_MASTER_CONF.in"
log " Generating Stacks master config file..."
sed -r \
-e "s!@@STACKS_CHAINSTATE_DIR@@!$STACKS_CHAINSTATE_DIR!g" \
-e "s!@@STACKS_PUBLIC_IP@@!$STACKS_MASTER_PUBLIC_IP!g" \
"$STACKS_MASTER_CONF".in \
> "$STACKS_MASTER_CONF"
logln "ok"
log "Starting Stacks master node..."
BLOCKSTACK_DEBUG=1 RUST_BACKTRACE=full stacks-node start --config="$STACKS_MASTER_CONF" >"$STACKS_MASTER_LOGFILE" 2>&1 &
local STACKS_PID=$!
logln "PID $STACKS_PID"
echo "$STACKS_PID"
return 0
}
start_stacks_miner_node() {
logln "Setting up Stacks miner node..."
test -f "$STACKS_MINER_CONF".in || exit_error "No such file or directory: $STACKS_MINER_CONF.in"
log " Generating Stacks miner config file..."
local STACKS_MASTER_IP="$1"
local BITCOIN_IP="$2"
local FAUCET_URL="$3"
local STACKS_MINER_SEED="$(blockstack-cli generate-sk | jq -r '.secretKey')"
sed -r \
-e "s/@@STACKS_MASTER_IP@@/$STACKS_MASTER_IP/g" \
-e "s/@@BITCOIN_IP@@/$BITCOIN_IP/g" \
-e "s/@@STACKS_MINER_SEED@@/$STACKS_MINER_SEED/g" \
-e "s!@@STACKS_CHAINSTATE_DIR@@!$STACKS_CHAINSTATE_DIR!g" \
"$STACKS_MINER_CONF".in \
> "$STACKS_MINER_CONF"
logln "ok"
local BTCADDR="$(blockstack-cli --testnet addresses "$STACKS_MINER_SEED" | jq -r '.BTC')"
local TIMEOUT=2
local TXID=""
local CONFIRMATIONS=0
log " Fetching BTC from the faucet for this miner ($BTCADDR)..."
for i in $(seq 1 10); do
TXID="$(curl -sf -X POST -d $BTCADDR$'\n' -H "content-type: text/plain" "$FAUCET_URL"/fund)"
local RC=$?
if [ $RC -ne 0 ]; then
# curl failed, or we didn't get 200. Try again
logln "FAILED! Try again in $TIMEOUT seconds..."
sleep $TIMEOUT
TIMEOUT=$((TIMEOUT + $RANDOM % TIMEOUT))
continue
fi
break
done
logln "txid $TXID"
log " Waiting for BTC to get confirmed..."
for i in $(seq 1 5); do
sleep 60
local CONFIRMATIONS="$(curl -sf "$FAUCET_URL"/confirmations/"$TXID")"
local RC=$?
if [ $RC -ne 0 ]; then
logln "FAILED! Try again (attempt $i)..."
continue
fi
if [ $CONFIRMATIONS -gt 0 ]; then
break
fi
done
if [ $CONFIRMATIONS -eq 0 ]; then
logln "FAILED"
return 1
fi
logln "ok"
log "Starting Stacks miner node..."
BLOCKSTACK_DEBUG=1 RUST_BACKTRACE=full stacks-node start --config="$STACKS_MINER_CONF" >"$STACKS_MINER_LOGFILE" 2>&1 &
local STACKS_PID=$!
logln "PID $STACKS_PID"
echo "$STACKS_PID"
return 0
}
start_stacks_follower_node() {
logln "Setting up Stacks follower node..."
test -f "$STACKS_FOLLOWER_CONF".in || exit_error "No such file or directory: $STACKS_FOLLOWER_CONF.in"
log " Generating Stacks follower config file..."
local STACKS_MASTER_IP="$1"
local BITCOIN_IP="$2"
sed -r \
-e "s/@@STACKS_MASTER_IP@@/$STACKS_MASTER_IP/g" \
-e "s/@@BITCOIN_IP@@/$BITCOIN_IP/g" \
-e "s!@@STACKS_CHAINSTATE_DIR@@!$STACKS_CHAINSTATE_DIR!g" \
"$STACKS_FOLLOWER_CONF".in \
> "$STACKS_FOLLOWER_CONF"
logln "ok"
log "Starting Stacks follower node..."
BLOCKSTACK_DEBUG=1 RUST_BACKTRACE=full stacks-node start --config="$STACKS_FOLLOWER_CONF" >"$STACKS_FOLLOWER_LOGFILE" 2>&1 &
local STACKS_PID=$!
logln "PID $STACKS_PID"
echo "$STACKS_PID"
return 0
}
check_if_running() {
local PID=$1
if [ $PID -gt 0 ]; then
kill -s 0 "$PID" 2>/dev/null
return $?
else
return 0
fi
}
kill_if_running() {
local SIG="$1"
local PID=$2
if [ $PID -gt 0 ]; then
check_if_running $PID
if [ $? -eq 0 ]; then
logln "Send $SIG to PID $PID"
kill -s "$SIG" "$PID" 2>/dev/null || true
fi
fi
}
usage() {
exit_error "Usage:\n $0 master\n $0 miner STACKS_MASTER_IP BITCOIN_IP FAUCET_URL\n $0 follower STACKS_MASTER_IP BITCOIN_IP\n\n"
}
if [ -z "$MODE" ]; then
usage
fi
for cmd in bitcoind bitcoin-cli bitcoin-neon-controller stacks-node blockstack-cli date jq grep sed kill cat curl faucet.sh seq; do
which $cmd 2>&1 >/dev/null || exit_error "Missing \"$cmd\""
done
BITCOIN_PID=0
BITCOIN_NEON_CONTROLLER_PID=0
STACKS_NODE_PID=0
FAUCET_PID=0
shutdown() {
echo "Sending SIGTERM to all processes"
for PID in $BITCOIN_PID $BITCOIN_NEON_CONTROLLER_PID $STACKS_NODE_PID $FAUCET_PID; do
kill_if_running SIGTERM $PID
done
sleep 5
echo "Sending SIGKILL to all processes"
for PID in $BITCOIN_PID $BITCOIN_NEON_CONTROLLER_PID $STACKS_NODE_PID $FAUCET_PID; do
kill_if_running SIGKILL $PID
done
exit 0
}
trap 'shutdown' INT
case "$MODE" in
master)
BITCOIN_PID="$(start_bitcoind)"
sleep 5
BITCOIN_NEON_CONTROLLER_PID="$(start_bitcoind_controller)"
sleep 5
FAUCET_PID="$(start_faucet)"
sleep 5
STACKS_NODE_PID="$(start_stacks_master_node)"
;;
miner)
test -n "$2" || usage
test -n "$3" || usage
test -n "$4" || usage
STACKS_NODE_PID="$(start_stacks_miner_node "$2" "$3" "$4")"
;;
follower)
test -n "$2" || usage
test -n "$3" || usage
STACKS_NODE_PID="$(start_stacks_follower_node "$2" "$3")"
;;
*)
usage
;;
esac
echo "Running!"
# reap children
while true; do
wait
sleep 5
ALL_GOOD=0
for PID in $BITCOIN_PID $BITCOIN_NEON_CONTROLLER_PID $STACKS_NODE_PID $FAUCET_PID; do
check_if_running $PID
if [ $? -ne 0 ]; then
ALL_GOOD=1
break
fi
done
if [ $ALL_GOOD -ne 0 ]; then
break
fi
done
echo "Some process exited unexpectedly"
shutdown

320
net-test/bin/txload.sh Executable file
View File

@@ -0,0 +1,320 @@
#!/bin/bash
FEE_RATE=300
MAX_CHAINING=5
CONFIRMATIONS=1
# grab fd 3 for curl
exec 3>&1
exit_error() {
printf "$1"
exit 1
}
usage() {
exit_error "$0 <private-key-hex> <stacks-url> <num-txs>\n"
}
MAIN_PRIVATE_KEY="$1"
STACKS_NODE_URL="$2"
NUM_TXS="$3"
if [ -z "$MAIN_PRIVATE_KEY" ] || [ -z "$STACKS_NODE_URL" ] || [ -z "$NUM_TXS" ]; then
usage
fi
set -uo pipefail
function log() {
printf "%s" "$1" >&2
}
function logln() {
printf "%s\n" "$1" >&2
}
make_token_transfer() {
local PRIVKEY="$1"
local NONCE="$2"
local DEST="$3"
local AMOUNT="$4"
local MEMO="load test $NONCE"
local TX="$(blockstack-cli --testnet token-transfer "$PRIVKEY" "$FEE_RATE" "$NONCE" "$DEST" "$AMOUNT" "$MEMO" 2>&1)"
local RC=$?
if [ $RC -ne 0 ]; then
logln "Failed to generate tx: blockstack-cli --testnet token-transfer $PRIVKEY $FEE_RATE $NONCE $DEST $AMOUNT \"$MEMO\""
return 1
fi
printf "$TX"
return 0
}
send_tx() {
local STACKS_NODE_URL="$1"
read TX
TXID="$(printf "$TX" | \
xxd -r -p | \
curl -s -X POST --data-binary @- -w "%{http_code}" -o >(cat >&3) -H "content-type: application/octet-stream" "$STACKS_NODE_URL"/v2/transactions 2>&1 | ( \
read HTTP_CODE
read BODY
if [ $HTTP_CODE -ne 200 ]; then
logln "Failed to send to node $STACKS_NODE_URL: server replied $HTTP_CODE. Tx was $TX"
logln "Error text: $BODY"
return 1
fi
echo "$BODY"
))"
local RC=$?
if [ $RC -ne 0 ]; then
return 1
fi
echo "$TXID"
return 0
}
get_chain_tip() {
local STACKS_NODE_URL="$1"
local TIP="$(curl -sf "$STACKS_NODE_URL"/v2/info | jq -r '.stacks_tip' 2>&1)"
local RC=$?
if [ $RC -ne 0 ]; then
logln "Failed to query chain tip on node $STACKS_NODE_URL: curl exited with code $RC"
return 1
fi
echo "$TIP"
return 0
}
get_account_nonce() {
local STACKS_NODE_URL="$1"
local ADDR="$2"
local NONCE="$(curl -sf "$STACKS_NODE_URL"/v2/accounts/"$ADDR""?proof=0" | jq -r '.nonce')"
local RC=$?
if [ $RC -ne 0 ]; then
logln "Failed to query account $ADDR on node $STACKS_NODE_URL: curl exited with code $RC"
return 1
fi
echo "$NONCE"
return 0
}
wait_for_new_stacks_block() {
local TIP="$1"
local STACKS_NODE_URL="$2"
while true; do
local CUR_TIP="$(get_chain_tip "$STACKS_NODE_URL")"
local RC=$?
if [ $RC -ne 0 ]; then
return 1
fi
if [[ "$CUR_TIP" != "$TIP" ]]; then
echo "$CUR_TIP"
return 0
fi
sleep 5
done
}
wait_for_confirmations() {
local CONFS=$1
local STACKS_NODE_URL="$2"
local TIP="$(get_chain_tip "$STACKS_NODE_URL")"
local CONF=0
for CONF in $(seq 0 $CONFS); do
TIP="$(wait_for_new_stacks_block "$TIP" "$STACKS_NODE_URL")"
RC=$?
if [ $RC -ne 0 ]; then
return 1
fi
done
echo "$TIP"
return 0
}
fund_keys() {
local MAIN_PRIVKEY="$1"
local AMOUNT="$2"
local STACKS_NODE_URL="$3"
local MAIN_ADDR="$(blockstack-cli --testnet addresses "$MAIN_PRIVKEY" | jq -r '.STX')"
local RC=$?
if [ $RC -ne 0 ]; then
logln "Failed to generate address for \"$MAIN_PRIVKEY\""
return 1
fi
local MAIN_NONCE="$(get_account_nonce "$STACKS_NODE_URL" "$MAIN_ADDR")"
RC=$?
if [ $RC -ne 0 ]; then
return 1
fi
local NEXT_PRIVKEY=""
local CHAIN_TIP="$(get_chain_tip "$STACKS_NODE_URL")"
local TX_COUNT=0
while read NEXT_PRIVKEY; do
local ADDR="$(blockstack-cli --testnet addresses "$NEXT_PRIVKEY" | jq -r '.STX')"
RC=$?
if [ $RC -ne 0 ]; then
logln "Failed to generate address for \"$NEXT_PRIVKEY\""
return 1
fi
log "Funding $NEXT_PRIVKEY ($ADDR) with $AMOUNT uSTX as tx #$MAIN_NONCE..."
TXID="$(make_token_transfer "$MAIN_PRIVKEY" "$MAIN_NONCE" "$ADDR" "$AMOUNT" | send_tx "$STACKS_NODE_URL")"
RC=$?
if [ $RC -ne 0 ]; then
logln "FAILED!"
logln "Failed to send fund-load token-transfer to $NEXT_PRIVKEY ($ADDR)"
return 1
fi
logln " ok"
echo "$NEXT_PRIVKEY"
MAIN_NONCE=$((MAIN_NONCE + 1))
TX_COUNT=$((TX_COUNT + 1))
if (( $TX_COUNT >= $MAX_CHAINING )); then
while true; do
log "Wait for at least $CONFIRMATIONS new Stacks blocks after $CHAIN_TIP..."
CHAIN_TIP="$(wait_for_confirmations $CONFIRMATIONS "$STACKS_NODE_URL")"
RC=$?
if [ $RC -ne 0 ]; then
logln "FAILED"
return 1
fi
logln "ok"
TX_COUNT=0
# wait for blockchain to catch up with us
local CUR_NONCE="$(get_account_nonce "$STACKS_NODE_URL" "$MAIN_ADDR")"
if [ $RC -ne 0 ]; then
return 1
fi
if (( $CUR_NONCE >= $MAIN_NONCE )); then
MAIN_NONCE=$CUR_NONCE
break
else
logln "Current nonce of fund key is $CUR_NONCE; need to wait until it is $MAIN_NONCE"
fi
done
fi
done
return 0
}
get_addrs_and_nonces() {
local NEXT_PRIVKEY=""
local STACKS_NODE_URL="$1"
while read NEXT_PRIVKEY; do
local ADDR="$(blockstack-cli --testnet addresses "$NEXT_PRIVKEY" | jq -r '.STX')"
RC=$?
if [ $RC -ne 0 ]; then
logln "Failed to generate address for \"$NEXT_PRIVKEY\""
return 1
fi
NONCE="$(get_account_nonce "$STACKS_NODE_URL" "$ADDR")"
if [ $RC -ne 0 ]; then
return 1
fi
echo "$NEXT_PRIVKEY $ADDR $NONCE"
done
return 0
}
tx_load() {
local DEST="$1"
local AMOUNT="$2"
local STACKS_NODE_URL="$3"
local NEXT_PRIVKEY=""
local RC=0
local NONCE=0
while read NEXT_PRIVKEY_ADDR_NONCE; do
set -- $NEXT_PRIVKEY_ADDR_NONCE
NEXT_PRIVKEY="$1"
ADDR="$2"
NONCE="$3"
log "Send $AMOUNT uSTX from $NEXT_PRIVKEY ($ADDR) to $DEST as tx #$NONCE..."
TXID="$(make_token_transfer "$NEXT_PRIVKEY" "$NONCE" "$DEST" "$AMOUNT" | send_tx "$STACKS_NODE_URL")"
RC=$?
if [ $RC -ne 0 ]; then
logln "FAILED!"
logln "Failed to send tx-load token-transfer from $NEXT_PRIVKEY ($ADDR) to $DEST"
return 1
fi
logln " ok"
done
return 0
}
generate_keys() {
local NUM_KEYS="$1"
local CNT=0
for CNT in $(seq 1 $NUM_KEYS); do
local PRIVKEY="$(blockstack-cli --testnet generate-sk | jq -r '.secretKey')"
RC=$?
if [ $RC -ne 0 ]; then
logln "Failed to generate private key"
return 1
fi
echo "$PRIVKEY"
done
return 0
}
DEST_ADDR="$(blockstack-cli --testnet generate-sk | jq -r '.secretKey,.stacksAddress' | ( \
read DEST_PRIVKEY
read DEST_ADDR
logln "Destination private key: $DEST_PRIVKEY ($DEST_ADDR)"
echo "$DEST_ADDR"
))"
AMOUNT=1
generate_keys "$NUM_TXS" | \
fund_keys "$MAIN_PRIVATE_KEY" "$((AMOUNT + FEE_RATE))" "$STACKS_NODE_URL" \
> /tmp/tx-load.keys
logln "Waiting for $CONFIRMATIONS confirmations before beginning"
TIP="$(wait_for_confirmations $CONFIRMATIONS "$STACKS_NODE_URL")"
RC=$?
if [ $RC -ne 0 ]; then
logln "FAILED"
exit 1
fi
cat /tmp/tx-load.keys | \
get_addrs_and_nonces "$STACKS_NODE_URL" | \
tx_load "$DEST_ADDR" "$AMOUNT" "$STACKS_NODE_URL"
exit $?

View File

@@ -0,0 +1,30 @@
[neon]
rpc_bind = "0.0.0.0:28443"
block_time = 30000
miner_address = "mmNe3BjtYa8ZWtpQxfpsE8aWKgHh77LYTT"
faucet_address = "n3k15aVS4rEWhVYn4YfAFjD8Em5mmsducg"
bitcoind_rpc_host = "127.0.0.1:18443"
bitcoind_rpc_user = "blockstack"
bitcoind_rpc_pass = "blockstacksystem"
genesis_timestamp = @@BITCOIN_NEON_CONTROLLER_GENESIS_TIMESTAMP@@
whitelisted_rpc_calls = [
"listunspent",
"importaddress",
"sendrawtransaction",
"getrawtransaction",
"scantxoutset",
"getrawmempool",
]
# Faucet:
# Private Key b20fea233f7718d956d2c5ed71889836ec872fec7d1de2068f19f40faf297f3001
# Public Key 02327e09dc18fee1d51fe2b19b487e01eba4e6adcb325bf102213fce7bbdcde6bd
# Address n3k15aVS4rEWhVYn4YfAFjD8Em5mmsducg
# Format p2pkh
# Network testnet
# Compressed true
# NOTE: the miner_address is the address of the Stacks master node's mining private key ("seed")
# NOTE: the following lines are meant to be grep'ed by the node runner:
# WIF cTYqAVPS7uJTAcxyzkXWjmRGoCjkPcb38wZVRjyXov1RiRDWPQj3

View File

@@ -0,0 +1,15 @@
server=1
regtest=1
rpcallowip=0.0.0.0/0
rpcallowip=::/0
rpcuser=blockstack
rpcpassword=blockstacksystem
txindex=1
listen=1
debug=1
rpcserialversion=0
datadir=@@BITCOIN_DATA_DIR@@
[regtest]
bind=0.0.0.0:18444
rpcbind=0.0.0.0:18443

View File

@@ -0,0 +1,30 @@
[node]
rpc_bind = "0.0.0.0:20443"
p2p_bind = "0.0.0.0:20444"
bootstrap_node = "04ee0b1602eb18fef7986887a7e8769a30c9df981d33c8380d255edef003abdcd243a0eb74afdf6740e6c423e62aec631519a24cf5b1d62bf8a3e06ddc695dcb77@@@STACKS_MASTER_IP:20444@@"
working_dir = "@@STACKS_CHAINSTATE_DIR@@"
[burnchain]
chain = "bitcoin"
mode = "krypton"
peer_host = "@@BITCOIN_IP@@"
rpc_port = 28443
peer_port = 18444
[[mstx_balance]]
# Private key: b8d99fd45da58038d630d9855d3ca2466e8e0f89d3894c4724f0efc9ff4b51f001
address = "ST2ZRX0K27GW0SP3GJCEMHD95TQGJMKB7G9Y0X1MH"
amount = 10000000000000000
[[mstx_balance]]
# Private key: 3a4e84abb8abe0c1ba37cef4b604e73c82b1fe8d99015cb36b029a65099d373601
address = "ST26FVX16539KKXZKJN098Q08HRX3XBAP541MFS0P"
amount = 10000000000000000
[[mstx_balance]]
# Private key: 052cc5b8f25b1e44a65329244066f76c8057accd5316c889f476d0ea0329632c01
address = "ST3CECAKJ4BH08JYY7W53MC81BYDT4YDA5M7S5F53"
amount = 10000000000000000
[[mstx_balance]]
# Private key: 9aef533e754663a453984b69d36f109be817e9940519cc84979419e2be00864801
address = "ST31HHVBKYCYQQJ5AQ25ZHA6W2A548ZADDQ6S16GP"
amount = 10000000000000000

View File

@@ -0,0 +1,35 @@
[node]
rpc_bind = "0.0.0.0:20443"
p2p_bind = "0.0.0.0:20444"
seed = "bbd40688849e9d0f3bc3132a576835df11bb84b307a5a0588d8fecc4d6ba32d201"
miner = true
working_dir = "@@STACKS_CHAINSTATE_DIR@@"
pox_sync_sample_secs = 10
wait_time_for_microblocks = 0
p2p_address = "@@STACKS_PUBLIC_IP@@:20444"
[burnchain]
chain = "bitcoin"
mode = "krypton"
peer_host = "0.0.0.0"
rpc_port = 28443
peer_port = 18444
poll_time_secs = 5
[[mstx_balance]]
# Private key: b8d99fd45da58038d630d9855d3ca2466e8e0f89d3894c4724f0efc9ff4b51f001
address = "ST2ZRX0K27GW0SP3GJCEMHD95TQGJMKB7G9Y0X1MH"
amount = 10000000000000000
[[mstx_balance]]
# Private key: 3a4e84abb8abe0c1ba37cef4b604e73c82b1fe8d99015cb36b029a65099d373601
address = "ST26FVX16539KKXZKJN098Q08HRX3XBAP541MFS0P"
amount = 10000000000000000
[[mstx_balance]]
# Private key: 052cc5b8f25b1e44a65329244066f76c8057accd5316c889f476d0ea0329632c01
address = "ST3CECAKJ4BH08JYY7W53MC81BYDT4YDA5M7S5F53"
amount = 10000000000000000
[[mstx_balance]]
# Private key: 9aef533e754663a453984b69d36f109be817e9940519cc84979419e2be00864801
address = "ST31HHVBKYCYQQJ5AQ25ZHA6W2A548ZADDQ6S16GP"
amount = 10000000000000000

View File

@@ -0,0 +1,36 @@
[node]
rpc_bind = "0.0.0.0:21443"
p2p_bind = "0.0.0.0:21444"
bootstrap_node = "04ee0b1602eb18fef7986887a7e8769a30c9df981d33c8380d255edef003abdcd243a0eb74afdf6740e6c423e62aec631519a24cf5b1d62bf8a3e06ddc695dcb77@@@STACKS_MASTER_IP@@:20444"
# bootstrap_node = "03ee0b1602eb18fef7986887a7e8769a30c9df981d33c8380d255edef003abdcd2@@@STACKS_MASTER_IP@@:20444"
seed = "@@STACKS_MINER_SEED@@"
miner = true
working_dir = "@@STACKS_CHAINSTATE_DIR@@"
pox_sync_sample_secs = 10
wait_time_for_microblocks = 0
[burnchain]
chain = "bitcoin"
mode = "krypton"
peer_host = "@@BITCOIN_IP@@"
rpc_port = 28443
peer_port = 18444
poll_time_secs = 5
[[mstx_balance]]
# Private key: b8d99fd45da58038d630d9855d3ca2466e8e0f89d3894c4724f0efc9ff4b51f001
address = "ST2ZRX0K27GW0SP3GJCEMHD95TQGJMKB7G9Y0X1MH"
amount = 10000000000000000
[[mstx_balance]]
# Private key: 3a4e84abb8abe0c1ba37cef4b604e73c82b1fe8d99015cb36b029a65099d373601
address = "ST26FVX16539KKXZKJN098Q08HRX3XBAP541MFS0P"
amount = 10000000000000000
[[mstx_balance]]
# Private key: 052cc5b8f25b1e44a65329244066f76c8057accd5316c889f476d0ea0329632c01
address = "ST3CECAKJ4BH08JYY7W53MC81BYDT4YDA5M7S5F53"
amount = 10000000000000000
[[mstx_balance]]
# Private key: 9aef533e754663a453984b69d36f109be817e9940519cc84979419e2be00864801
address = "ST31HHVBKYCYQQJ5AQ25ZHA6W2A548ZADDQ6S16GP"
amount = 10000000000000000

4
net-test/mnt/cleanup.sh Executable file
View File

@@ -0,0 +1,4 @@
#!/bin/bash
mkdir -p archive
mv bitcoin-1* stacks-chainstate-* archive