Skip to content

Commit 26cf9a2

Browse files
authored
Merge pull request #1980 from blockstack/feat/network-test-framework
Feat/network test framework
2 parents acbb500 + 1825fbc commit 26cf9a2

40 files changed

+1907
-296
lines changed

net-test/README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Stacks Network Testing
2+
3+
A rudimentary set of tools for testing multiple Stacks nodes at once, locally.
4+
Relevant files:
5+
6+
* `bin/start.sh` -- start up a master node, a miner node, or a follower node, as
7+
well as ancilliary processes.
8+
* `bin/faucet.sh` -- a rudimentary Bitcoin faucet.
9+
* `bin/txload.sh` -- a rudimentary Stacks transaction generator and load-tester.
10+
* `etc/*.in` -- templates that `bin/start.sh` uses to create configuration
11+
files.
12+
13+
To use, you will need to install `stacks-node`, `blockstack-cli`,
14+
`bitcoin-neon-controller`, and `bin/faucet.sh` to somewhere in your `$PATH`.
15+
You will also need a recent `bitcoind` and `bitcoin-cli`.

net-test/bin/config.sh

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# local config
2+
__ROOT="$(realpath "$(pwd)"/..)"
3+
__ETC="$__ROOT/etc"
4+
__MNT="$__ROOT/mnt"
5+
6+
BITCOIN_CONF="$__ETC/bitcoin.conf"
7+
BITCOIN_CONTROLLER_CONF="$__ETC/bitcoin-neon-controller.toml"
8+
STACKS_MASTER_CONF="$__ETC/stacks-master.toml"
9+
STACKS_MINER_CONF="$__ETC/stacks-miner.toml"
10+
STACKS_FOLLOWER_CONF="$__ETC/stacks-follower.toml"
11+
12+
BITCOIN_LOGFILE="$__MNT/bitcoin.log"
13+
BITCOIN_NEON_CONTROLLER_LOGFILE="$__MNT/bitcoin-neon-controller.log"
14+
15+
STACKS_MASTER_LOGFILE="$__MNT/stacks-node-master.log"
16+
STACKS_MINER_LOGFILE="$__MNT/stacks-node-miner.log"
17+
STACKS_FOLLOWER_LOGFILE="$__MNT/stacks-node-follower.log"
18+
FAUCET_LOGFILE="$__MNT/faucet.log"
19+
20+
STACKS_MASTER_PUBLIC_IP="127.0.0.1"
21+
22+
FAUCET_PORT=8080
23+
24+
__NOW="$(date +%s)"
25+
STACKS_CHAINSTATE_DIR="$__MNT/stacks-chainstate-$__NOW"
26+
BITCOIN_DATA_DIR="$__MNT/bitcoin-$__NOW"
27+
28+
echo >&2 "Loaded external config"

net-test/bin/faucet.sh

Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
#!/usr/bin/env bash
2+
3+
# Yup, it's a faucet HTTP server written in bash. This is what my life has come to.
4+
5+
MAX_BODY_LENGTH=65536
6+
FAUCET_AMOUNT="1.0"
7+
8+
MODE="$1"
9+
BITCOIN_CONF="$2"
10+
11+
exit_error() {
12+
printf "$1" >&2
13+
exit 1
14+
}
15+
16+
for cmd in ncat bitcoin-cli egrep grep tr dd sed cut date; do
17+
which $cmd >/dev/null 2>&1 || exit_error "Missing command: $cmd"
18+
done
19+
20+
if [ $(echo ${BASH_VERSION} | cut -d '.' -f 1) -lt 4 ]; then
21+
exit_error "This script requires Bash 4.x or higher"
22+
fi
23+
24+
set -uo pipefail
25+
26+
log() {
27+
printf >&2 "%s\n" "$1"
28+
}
29+
30+
http_ok() {
31+
local CONTENT_LENGTH
32+
local CONTENT_TYPE
33+
34+
CONTENT_LENGTH=$1
35+
CONTENT_TYPE=$2
36+
printf "HTTP/1.0 200 OK\r\nContent-Length: $CONTENT_LENGTH\r\nContent-Type: $CONTENT_TYPE\r\n\r\n"
37+
}
38+
39+
http_401() {
40+
printf "HTTP/1.0 401 Unsupported Method\r\n\r\n"
41+
}
42+
43+
http_500() {
44+
local ERR="$1"
45+
local ERR_LEN=${#ERR}
46+
printf "HTTP/1.0 500 Internal Server error\r\nContent-Length: $ERR_LEN\r\nContent-Type: text/plain\r\n\r\n$ERR"
47+
}
48+
49+
http_404() {
50+
printf "HTTP/1.0 404 Not Found\r\n\r\n"
51+
}
52+
53+
get_ping() {
54+
http_ok 5 "text/plain"
55+
printf "alive"
56+
return 0
57+
}
58+
59+
get_bitcoin_ping() {
60+
bitcoin-cli -conf="$BITCOIN_CONF" ping >/dev/null 2>&1
61+
if [ $? -eq 0 ]; then
62+
local MSG="Bitcoind appears to be running"
63+
http_ok ${#MSG} "text/plain"
64+
echo "$MSG"
65+
return 0
66+
else
67+
http_500 "Bitcoind appears to be stopped"
68+
return 1
69+
fi
70+
}
71+
72+
get_balance() {
73+
BALANCE="$(bitcoin-cli -conf="$BITCOIN_CONF" getbalance 2>&1)"
74+
if [ $? -eq 0 ]; then
75+
http_ok "${#BALANCE}" "text/plain"
76+
echo "$BALANCE"
77+
return 0
78+
else
79+
http_500 "$BALANCE"
80+
return 1
81+
fi
82+
}
83+
84+
get_utxos() {
85+
local ADDR="$1"
86+
UTXOS="$(bitcoin-cli -conf="$BITCOIN_CONF" listunspent 1 1000000 "[\"$ADDR\"]" 2>&1)"
87+
if [ $? -eq 0 ]; then
88+
http_ok ${#UTXOS} "application/json"
89+
echo "$UTXOS"
90+
return 0
91+
else
92+
http_500 "$UTXOS"
93+
return 1
94+
fi
95+
}
96+
97+
get_confirmations() {
98+
local TXID="$1"
99+
CONFIRMATIONS="$(bitcoin-cli -conf="$BITCOIN_CONF" gettransaction "$TXID" | jq -r '.confirmations')"
100+
RC=$?
101+
if [ $RC -eq 0 ]; then
102+
http_ok ${#CONFIRMATIONS} "text/plain"
103+
echo "$CONFIRMATIONS"
104+
return 0
105+
elif [ $RC -eq 1 ]; then
106+
http_500 "$CONFIRMATIONS"
107+
return 1
108+
else
109+
http_404
110+
return 2
111+
fi
112+
}
113+
114+
post_sendbtc() {
115+
local ADDR
116+
117+
# format: address\n
118+
read ADDR
119+
TXID="$(bitcoin-cli -conf="$BITCOIN_CONF" sendtoaddress "$ADDR" "$FAUCET_AMOUNT" 2>&1)"
120+
if [ $? -ne 0 ]; then
121+
http_500 "$TXID"
122+
return 1
123+
fi
124+
125+
ERR="$(bitcoin-cli -conf="$BITCOIN_CONF" importaddress "$ADDR" 2>&1)"
126+
if [ $? -ne 0 ]; then
127+
http_500 "$ERR"
128+
return 1
129+
fi
130+
131+
http_ok ${#TXID} "text/plain"
132+
echo "$TXID"
133+
return 0
134+
}
135+
136+
parse_request() {
137+
local REQLINE
138+
local VERB=""
139+
local REQPATH=""
140+
local CONTENT_TYPE=""
141+
local CONTENT_LENGTH=0
142+
143+
while read REQLINE; do
144+
# trim trailing whitespace
145+
REQLINE="${REQLINE%"${REQLINE##*[![:space:]]}"}"
146+
if [ -z "$REQLINE" ]; then
147+
break
148+
fi
149+
150+
# log " reqline = '$REQLINE'"
151+
152+
TOK="$(echo "$REQLINE" | egrep "GET|POST" | sed -r 's/^(GET|POST)[ ]+([^ ]+)[ ]+HTTP\/1.(0|1)$/\1 \2/g')"
153+
if [ -n "$TOK" ] && [ -z "$VERB" ] && [ -z "$REQPATH" ]; then
154+
set -- $TOK
155+
VERB="$1"
156+
REQPATH="$2"
157+
continue
158+
fi
159+
160+
TOK="$(echo "$REQLINE" | grep -i "content-type" | cut -d ' ' -f 2)"
161+
if [ -n "$TOK" ] && [ -z "$CONTENT_TYPE" ]; then
162+
CONTENT_TYPE="${TOK,,}"
163+
continue
164+
fi
165+
166+
TOK="$(echo "$REQLINE" | grep -i "content-length" | cut -d ' ' -f 2)"
167+
if [ -n "$TOK" ] && [ $CONTENT_LENGTH -eq 0 ]; then
168+
if [[ "$TOK" =~ ^[0-9]+$ ]]; then
169+
CONTENT_LENGTH="$TOK"
170+
continue
171+
fi
172+
fi
173+
done
174+
175+
if [ $CONTENT_LENGTH -gt $MAX_BODY_LENGTH ]; then
176+
exit 1
177+
fi
178+
179+
if [ -z "$VERB" ] || [ -z "$REQPATH" ]; then
180+
exit 1
181+
fi
182+
183+
# log " verb = '$VERB', reqpath = '$REQPATH', content-type = '$CONTENT_TYPE', content-length = '$CONTENT_LENGTH'"
184+
185+
printf "$VERB\n$REQPATH\n$CONTENT_TYPE\n$CONTENT_LENGTH\n"
186+
dd bs=$CONTENT_LENGTH 2>/dev/null
187+
return 0
188+
}
189+
190+
handle_request() {
191+
local VERB
192+
local REQPATH
193+
local CONTENT_TYPE
194+
local CONTENT_LENGTH
195+
local STATUS=200
196+
197+
read VERB
198+
read REQPATH
199+
read CONTENT_TYPE
200+
read CONTENT_LENGTH
201+
202+
case "$VERB" in
203+
GET)
204+
case "$REQPATH" in
205+
/ping)
206+
get_ping
207+
if [ $? -ne 0 ]; then
208+
STATUS=500
209+
fi
210+
;;
211+
212+
/bitcoin)
213+
get_bitcoin_ping
214+
if [ $? -ne 0 ]; then
215+
STATUS=500
216+
fi
217+
;;
218+
219+
/balance)
220+
get_balance
221+
if [ $? -ne 0 ]; then
222+
STATUS=500
223+
fi
224+
;;
225+
226+
/confirmations/*)
227+
TXID="${REQPATH#/confirmations/}"
228+
get_confirmations "$TXID"
229+
if [ $? -eq 1 ]; then
230+
STATUS=500
231+
elif [ $? -eq 2 ]; then
232+
STATUS=404
233+
fi
234+
;;
235+
236+
/utxos/*)
237+
ADDR="${REQPATH#/utxos/}"
238+
get_utxos "$ADDR"
239+
if [ $? -ne 0 ]; then
240+
STATUS=500
241+
fi
242+
;;
243+
*)
244+
http_404
245+
STATUS=404
246+
;;
247+
esac
248+
;;
249+
POST)
250+
case "$REQPATH" in
251+
/fund)
252+
if [ "$CONTENT_TYPE" != "text/plain" ]; then
253+
http_401
254+
STATUS=401
255+
else
256+
post_sendbtc
257+
if [ $? -ne 0 ]; then
258+
STATUS=500
259+
fi
260+
fi
261+
;;
262+
*)
263+
http_404
264+
STATUS=404
265+
;;
266+
esac
267+
;;
268+
*)
269+
http_401
270+
STATUS=404
271+
;;
272+
esac
273+
274+
log "[$(date +%s)] $VERB $REQPATH ($CONTENT_LENGTH bytes) - $STATUS"
275+
}
276+
277+
usage() {
278+
exit_error "Usage:\n $0 serve </path/to/bitcoin.conf>\n $0 <port> </path/to/bitcoin.conf>\n"
279+
}
280+
281+
if [ -z "$MODE" ] || [ -z "$BITCOIN_CONF" ]; then
282+
usage
283+
fi
284+
285+
if [ "$MODE" = "serve" ]; then
286+
parse_request | handle_request
287+
exit 0
288+
elif [ "$MODE" = "parse" ]; then
289+
# undocumented test mode
290+
parse_request
291+
exit 0
292+
fi
293+
294+
# $MODE will be the port number in this usage path
295+
if ! [[ $MODE =~ ^[0-9]+$ ]]; then
296+
usage
297+
fi
298+
299+
exec ncat -k -l -p "$MODE" -c "$BASH \"$0\" serve \"$BITCOIN_CONF\""

0 commit comments

Comments
 (0)