validator.sh 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. #!/usr/bin/env bash
  2. #
  3. # Start a validator
  4. #
  5. here=$(dirname "$0")
  6. # shellcheck source=multinode-demo/common.sh
  7. source "$here"/common.sh
  8. args=(
  9. --max-genesis-archive-unpacked-size 1073741824
  10. --no-poh-speed-test
  11. --no-os-network-limits-test
  12. )
  13. airdrops_enabled=1
  14. node_sol=500 # 500 SOL: number of SOL to airdrop the node for transaction fees and vote account rent exemption (ignored if airdrops_enabled=0)
  15. label=
  16. identity=
  17. vote_account=
  18. no_restart=0
  19. gossip_entrypoint=
  20. ledger_dir=
  21. usage() {
  22. if [[ -n $1 ]]; then
  23. echo "$*"
  24. echo
  25. fi
  26. cat <<EOF
  27. usage: $0 [OPTIONS] [cluster entry point hostname]
  28. Start a validator with no stake
  29. OPTIONS:
  30. --ledger PATH - store ledger under this PATH
  31. --init-complete-file FILE - create this file, if it doesn't already exist, once node initialization is complete
  32. --label LABEL - Append the given label to the configuration files, useful when running
  33. multiple validators in the same workspace
  34. --node-sol SOL - Number of SOL this node has been funded from the genesis config (default: $node_sol)
  35. --no-voting - start node without vote signer
  36. --rpc-port port - custom RPC port for this node
  37. --no-restart - do not restart the node if it exits
  38. --no-airdrop - The genesis config has an account for the node. Airdrops are not required.
  39. EOF
  40. exit 1
  41. }
  42. maybeRequireTower=true
  43. positional_args=()
  44. while [[ -n $1 ]]; do
  45. if [[ ${1:0:1} = - ]]; then
  46. # validator.sh-only options
  47. if [[ $1 = --label ]]; then
  48. label="-$2"
  49. shift 2
  50. elif [[ $1 = --no-restart ]]; then
  51. no_restart=1
  52. shift
  53. elif [[ $1 = --node-sol ]]; then
  54. node_sol="$2"
  55. shift 2
  56. elif [[ $1 = --no-airdrop ]]; then
  57. airdrops_enabled=0
  58. shift
  59. # agave-validator options
  60. elif [[ $1 = --expected-genesis-hash ]]; then
  61. args+=("$1" "$2")
  62. shift 2
  63. elif [[ $1 = --expected-shred-version ]]; then
  64. args+=("$1" "$2")
  65. shift 2
  66. elif [[ $1 = --identity ]]; then
  67. identity=$2
  68. args+=("$1" "$2")
  69. shift 2
  70. elif [[ $1 = --authorized-voter ]]; then
  71. args+=("$1" "$2")
  72. shift 2
  73. elif [[ $1 = --authorized-withdrawer ]]; then
  74. authorized_withdrawer=$2
  75. shift 2
  76. elif [[ $1 = --vote-account ]]; then
  77. vote_account=$2
  78. args+=("$1" "$2")
  79. shift 2
  80. elif [[ $1 = --init-complete-file ]]; then
  81. args+=("$1" "$2")
  82. shift 2
  83. elif [[ $1 = --ledger ]]; then
  84. ledger_dir=$2
  85. shift 2
  86. elif [[ $1 = --entrypoint ]]; then
  87. gossip_entrypoint=$2
  88. args+=("$1" "$2")
  89. shift 2
  90. elif [[ $1 = --no-snapshot-fetch ]]; then
  91. args+=("$1")
  92. shift
  93. elif [[ $1 = --no-voting ]]; then
  94. args+=("$1")
  95. shift
  96. elif [[ $1 = --dev-no-sigverify ]]; then
  97. args+=("$1")
  98. shift
  99. elif [[ $1 = --dev-halt-at-slot ]]; then
  100. args+=("$1" "$2")
  101. shift 2
  102. elif [[ $1 = --rpc-port ]]; then
  103. args+=("$1" "$2")
  104. shift 2
  105. elif [[ $1 = --rpc-faucet-address ]]; then
  106. args+=("$1" "$2")
  107. shift 2
  108. elif [[ $1 = --accounts ]]; then
  109. args+=("$1" "$2")
  110. shift 2
  111. elif [[ $1 = --gossip-port ]]; then
  112. args+=("$1" "$2")
  113. shift 2
  114. elif [[ $1 = --dynamic-port-range ]]; then
  115. args+=("$1" "$2")
  116. shift 2
  117. elif [[ $1 = --snapshot-interval-slots ]]; then
  118. args+=("$1" "$2")
  119. shift 2
  120. elif [[ $1 = --maximum-snapshots-to-retain ]]; then
  121. args+=("$1" "$2")
  122. shift 2
  123. elif [[ $1 = --limit-ledger-size ]]; then
  124. args+=("$1" "$2")
  125. shift 2
  126. elif [[ $1 = --no-rocksdb-compaction ]]; then
  127. args+=("$1")
  128. shift
  129. elif [[ $1 = --enable-rpc-transaction-history ]]; then
  130. args+=("$1")
  131. shift
  132. elif [[ $1 = --enable-extended-tx-metadata-storage ]]; then
  133. args+=("$1")
  134. shift
  135. elif [[ $1 = --skip-poh-verify ]]; then
  136. args+=("$1")
  137. shift
  138. elif [[ $1 = --tpu-disable-quic ]]; then
  139. args+=("$1")
  140. shift
  141. elif [[ $1 = --tpu-enable-udp ]]; then
  142. args+=("$1")
  143. shift
  144. elif [[ $1 = --rpc-send-batch-ms ]]; then
  145. args+=("$1" "$2")
  146. shift 2
  147. elif [[ $1 = --rpc-send-batch-size ]]; then
  148. args+=("$1" "$2")
  149. shift 2
  150. elif [[ $1 = --log ]]; then
  151. args+=("$1" "$2")
  152. shift 2
  153. elif [[ $1 = --known-validator ]]; then
  154. args+=("$1" "$2")
  155. shift 2
  156. elif [[ $1 = --max-genesis-archive-unpacked-size ]]; then
  157. args+=("$1" "$2")
  158. shift 2
  159. elif [[ $1 == --wait-for-supermajority ]]; then
  160. args+=("$1" "$2")
  161. shift 2
  162. elif [[ $1 == --expected-bank-hash ]]; then
  163. args+=("$1" "$2")
  164. shift 2
  165. elif [[ $1 == --accounts-db-skip-shrink ]]; then
  166. args+=("$1")
  167. shift
  168. elif [[ $1 == --skip-require-tower ]]; then
  169. maybeRequireTower=false
  170. shift
  171. elif [[ $1 == --block-production-method ]]; then
  172. args+=("$1" "$2")
  173. shift 2
  174. elif [[ $1 == --enable-scheduler-bindings ]]; then
  175. args+=("$1")
  176. shift
  177. elif [[ $1 == --transaction-structure ]]; then
  178. args+=("$1" "$2")
  179. shift 2
  180. elif [[ $1 == --wen-restart ]]; then
  181. args+=("$1" "$2")
  182. shift 2
  183. elif [[ $1 == --wen-restart-coordinator ]]; then
  184. args+=("$1" "$2")
  185. shift 2
  186. elif [[ $1 = -h ]]; then
  187. usage "$@"
  188. else
  189. echo "Unknown argument: $1"
  190. exit 1
  191. fi
  192. else
  193. positional_args+=("$1")
  194. shift
  195. fi
  196. done
  197. if [[ ${#positional_args[@]} -gt 1 ]]; then
  198. usage "$@"
  199. fi
  200. if [[ -n $REQUIRE_LEDGER_DIR ]]; then
  201. if [[ -z $ledger_dir ]]; then
  202. usage "Error: --ledger not specified"
  203. fi
  204. SOLANA_CONFIG_DIR="$ledger_dir"
  205. fi
  206. if [[ -n $REQUIRE_KEYPAIRS ]]; then
  207. if [[ -z $identity ]]; then
  208. usage "Error: --identity not specified"
  209. fi
  210. if [[ -z $vote_account ]]; then
  211. usage "Error: --vote-account not specified"
  212. fi
  213. if [[ -z $authorized_withdrawer ]]; then
  214. usage "Error: --authorized_withdrawer not specified"
  215. fi
  216. fi
  217. if [[ -z "$ledger_dir" ]]; then
  218. ledger_dir="$SOLANA_CONFIG_DIR/validator$label"
  219. fi
  220. mkdir -p "$ledger_dir"
  221. if [[ -n $gossip_entrypoint ]]; then
  222. # Prefer the --entrypoint argument if supplied...
  223. if [[ ${#positional_args[@]} -gt 0 ]]; then
  224. usage "$@"
  225. fi
  226. else
  227. # ...but also support providing the entrypoint's hostname as the first
  228. # positional argument
  229. entrypoint_hostname=${positional_args[0]}
  230. if [[ -z $entrypoint_hostname ]]; then
  231. gossip_entrypoint=127.0.0.1:8001
  232. else
  233. gossip_entrypoint="$entrypoint_hostname":8001
  234. fi
  235. fi
  236. faucet_address="${gossip_entrypoint%:*}":9900
  237. : "${identity:=$ledger_dir/identity.json}"
  238. : "${vote_account:=$ledger_dir/vote-account.json}"
  239. : "${authorized_withdrawer:=$ledger_dir/authorized-withdrawer.json}"
  240. default_arg --entrypoint "$gossip_entrypoint"
  241. if ((airdrops_enabled)); then
  242. default_arg --rpc-faucet-address "$faucet_address"
  243. fi
  244. default_arg --identity "$identity"
  245. default_arg --vote-account "$vote_account"
  246. default_arg --ledger "$ledger_dir"
  247. default_arg --log -
  248. default_arg --full-rpc-api
  249. default_arg --no-incremental-snapshots
  250. default_arg --allow-private-addr
  251. if [[ $maybeRequireTower = true ]]; then
  252. default_arg --require-tower
  253. fi
  254. program=$agave_validator
  255. set -e
  256. PS4="$(basename "$0"): "
  257. pid=
  258. kill_node() {
  259. # Note: do not echo anything from this function to ensure $pid is actually
  260. # killed when stdout/stderr are redirected
  261. set +ex
  262. if [[ -n $pid ]]; then
  263. declare _pid=$pid
  264. pid=
  265. kill "$_pid" || true
  266. wait "$_pid" || true
  267. fi
  268. }
  269. kill_node_and_exit() {
  270. kill_node
  271. exit
  272. }
  273. trap 'kill_node_and_exit' INT TERM ERR
  274. wallet() {
  275. (
  276. set -x
  277. $solana_cli --keypair "$identity" --url "$rpc_url" "$@"
  278. )
  279. }
  280. setup_validator_accounts() {
  281. declare node_sol=$1
  282. if [[ -n "$SKIP_ACCOUNTS_CREATION" ]]; then
  283. return 0
  284. fi
  285. if ! wallet vote-account "$vote_account"; then
  286. if ((airdrops_enabled)); then
  287. echo "Adding $node_sol to validator identity account:"
  288. (
  289. set -x
  290. $solana_cli \
  291. --keypair "$SOLANA_CONFIG_DIR/faucet.json" --url "$rpc_url" \
  292. transfer --allow-unfunded-recipient "$identity" "$node_sol"
  293. ) || return $?
  294. fi
  295. echo "Creating validator vote account"
  296. wallet create-vote-account "$vote_account" "$identity" "$authorized_withdrawer" || return $?
  297. fi
  298. echo "Validator vote account configured"
  299. echo "Validator identity account balance:"
  300. wallet balance || return $?
  301. return 0
  302. }
  303. # shellcheck disable=SC2086
  304. rpc_url=$($solana_gossip --allow-private-addr rpc-url --timeout 180 --entrypoint "$gossip_entrypoint")
  305. [[ -r "$identity" ]] || $solana_keygen new --no-passphrase -so "$identity"
  306. [[ -r "$vote_account" ]] || $solana_keygen new --no-passphrase -so "$vote_account"
  307. [[ -r "$authorized_withdrawer" ]] || $solana_keygen new --no-passphrase -so "$authorized_withdrawer"
  308. setup_validator_accounts "$node_sol"
  309. while true; do
  310. echo "$PS4$program ${args[*]}"
  311. $program "${args[@]}" &
  312. pid=$!
  313. echo "pid: $pid"
  314. if ((no_restart)); then
  315. wait "$pid"
  316. exit $?
  317. fi
  318. while true; do
  319. if [[ -z $pid ]] || ! kill -0 "$pid"; then
  320. echo "############## validator exited, restarting ##############"
  321. break
  322. fi
  323. sleep 1
  324. done
  325. kill_node
  326. done