Skip to main content
Glama
count-votes.sh52.5 kB
#!/bin/bash ########################################################################################################### # # Vote tallying script for SIP 012 # # Everyone who has called `stack-stx` or `delegate-stack-stx` in the prior two full reward cycles from # the vote tallying block is eligible to cast a vote. People who stacked other peoples' STX via the # latter vote on behalf of the STX they represent. # # Votes are weighted by stacked STX. The number of votes a stacker has is equal to the number of STX # they stacked in the last full reward cycle in which they stacked. # # Votes are cast by sending Bitcoin transactions. A well-formed voting transaction will have at least one # UTXO which pays a dust amount of BTC to either a "yes" address or a "no" address. The "yes" address is # 111111111111111111112czxoHN, and the "no" address is 111111111111111111112kmzDG2. # # Votes are tallied from the `stacking-state` data map in the `.pox` smart contract, by means of calling # the read-only function `get-stacker-info`. There are two ways to obtain the stacker address argument: # # * The stacker's voting transaction's scriptSig is generated directly from the stacker address. In this # case, the address is calculated from the public key(s) in the scriptSig. # # * The stacker's voting transaction's scriptSig is generated from the PoX reward address. In this case, # this script looks up the corresponding stacker address from a given CSV table generated from the # blockchain's stacking event stream. # # Once the stacker address is known, the number of locked STX is queried from the `stacking-state` map and # added to the global "yes" or "no" tallies. # ########################################################################################################### # # To run this script, you will need: # * a fully-sync'ed bitcoin node, with: # * txindex=1 set in its config file # * the "yes" and "no" address' UTXOs tracked # * a fully-sync'ed stacks node # * a list of stacker records taken from the logfile. Records must take the form of either: # * a stacker record, containing a JSON object with {"address": $addr, "type": "stacker", "locked": $ustx, "until": $unlock_burn_height} # * a delegate record, containing a JSON object with {"address": $addr, "type": "delegation", "until": $unlocK_burn_height} # * a recent version of the node.js CLI # * Node.js packages `bitcoinjs-lib`, `c32check`, and `@stacks/transactions` installed to your NODE_PATH # * the binaries `bitcoin-cli`, `jq`, `grep`, `date`, `wc`, and `xargs` # # USAGE: ./count-votes.sh TABULATION_DIR CHAIN_TIP_HASH CHAIN_TIP_BURN_HEIGHT < STACKERS.json # # To get the tabulation for reward cycle 19, run: # # $ cat stackers-19.json delegating.json | ./count-votes.sh /tmp/tally-19 4933b0b002a854a9ca7305166238d17be018ce54e415530540aa7e620e9cd86d 705850 # # To get the tabulation for reward cycle 20, run: # # $ cat stackers-20.json delegating.json | ./count-votes.sh /tmp/tally-20 13f60e5857a64485572372eeee9a204226e199d5051a2f2d6babc7b173854a89 711001 # # The script may take a while to run, depending on how fast your node is. # # The script is idempotent, and will attempt to reuse data it obtained in TABULATION_DIR. # # BUGS # # * Sometimes the act of determining a stacker's voting information can fail silently. You can detect this # by comparing the number of lines in TABULATION_DIR/stackers.json to the number of "stacker" records. If # there are fewer lines in TABULATION_DIR/stackers.json, then just re-run the script. It will only fetch # the missing stacker.json records. # ########################################################################################################### # # To use this script to tabulate SIP 012's votes, you need to gather the list of Stackers for a given # reward cycle that could count towards the vote. In SIP 012, this means that if a Stacker is # present in either of the two prior full reward cycles, then their *latest* quantity of STX Stacked # needs to be considered (and they should not be counted in an earlier reward cycle's tabulation). # Given SIP 012's timeline, this means considering Stackers in reward cycles 19 and 20. If a Stacker # Stacked in both, then only the STX Stacked in reward cycle 20 is considered. # # Examples: # # *If Alice Stacked 100,000 STX in the earlier reward cycle, but 50,000 STX in the later reward cycle, # she would be excluded from the tabulation of the earlier reward cycle and only included in the later # reward cycle. # # * If Bob Stacked 100,000 STX in the earlier reward cycle, and did not Stack in the later reward cycle, # then his 100,000 STX would be considered in the earlier reward cycle's tabulation. He would not be # considered in the later reward cycle's tabulation. # # * If Charles Stacked 100,000 STX in the later reward cycle, but did not Stack in the earlier reward cycle, # then his 100,000 STX would be considered in the later reward cycle's tabulation. He would not be considered # in the earlier reward cycle's tabulation. # # By running this script with the list of eligible Stackers in the earlier and later full reward cycles, you # can take the resulting tabulations of both reward cycles and add them together to get the final vote tabulation. # ########################################################################################################### TESTNET=0 BITCOIN_CONF=/etc/bitcoin/bitcoin.conf STX_NODE="http://localhost:20443" POX_ADDR="SP000000000000000000002Q6VF78" YES_ADDR="111111111111111111112czxoHN" NO_ADDR="111111111111111111112kmzDG2" if [ $TESTNET -ne 0 ]; then POX_ADDR="ST000000000000000000002AMW42H" YES_ADDR="mfWxJ45yp2SFn7UciZyNpvDKrzbjYw8w7S" NO_ADDR="mfWxJ45yp2SFn7UciZyNpvDKrzbjhQ15VR" fi set -oue pipefail bitcoin_cli() { # Run bitcoin-cli with default settings # # $@: bitcoin-cli args # stdin: none # stdout: bitcoin-cli command output # stderr: bitcoin-cli error message # return: 0 on success, nonzero on error bitcoin-cli -conf="$BITCOIN_CONF" "$@" } get_utxos() { # Get the UTXOs for an address # # $1: Bitcoin address # stdin: none # stdout: JSON-encoded unspents from the address # stderr: bitcoin-cli error message # return: 0 on success, nonzero on error local address="$1" bitcoin_cli listunspent 0 1000000 "[\"$address\"]" } get_scriptSigs() { # Decode UTXOs into scriptSigs # # stdin: JSON-encoded unspent outputs as an array of objects # stdout: newline-separated list of scriptSig JSON objects in the form of '{ "hex": ..., "asm": ... "address": ...}'. "address" is optional. # stderr: none # return: 0 on success, nonzero on error local txid="" local vout=0 local json="" local scriptPubKey="" local address="" jq -r '.[].txid' | while read -r txid; do json="$(bitcoin_cli getrawtransaction "$txid" 1)" # if this is a segwit tx, go and get the tx that funded it # to find the segwit-over-p2sh address (if it exists) if [ "$(echo "$json" | jq -r -c '.vin[0].txinwitness')" != "null" ]; then txid="$(echo "$json" | jq -r -c '.vin[0].txid')" vout="$(echo "$json" | jq -r -c '.vin[0].vout')" json="$(bitcoin_cli getrawtransaction "$txid" 1)" scriptPubKey="$(echo "$json" | jq -r -c ".vout[$vout].scriptPubKey")" if [ "$(echo "$scriptPubKey" | jq -r -c '.type')" = "scripthash" ]; then address="$(echo "$scriptPubKey" | jq -r -c '.addresses[0]')" if [ "$address" != "null" ]; then echo "{\"hex\": \"00\", \"asm\": \"00\", \"address\": \"$address\"}" fi fi else bitcoin_cli getrawtransaction "$txid" 1 | jq -r -c '.vin[0].scriptSig' fi done } btc_addr_to_stx_addr() { # Convert a Bitcoin address to a Stacks address # # $1: bitcoin address # stdin: none # stdout: the Stacks address # stderr: none # return: 0 on success, nonzero on error local btc_addr="$1" echo " c32 = require('c32check'); console.log(c32.b58ToC32(\"$btc_addr\")) " | node - 2>/dev/null } pubkey_to_btc_addr() { # Convert a public key to a Bitcoin address # # $1: secp256k1 public key # stdin: none # stdout: the Stacks address # stderr: none # return: 0 on success, nonzero on error local pubkey="$1" echo " const bitcoin = require('bitcoinjs-lib'); const pubkey = Buffer.from(\"$pubkey\", \"hex\"); const { address } = bitcoin.payments.p2pkh({ pubkey }); console.log(address); " | node - 2>/dev/null } make_btc_addr() { # Make a bitcoin address from a hex-encoded version byte and hash160 # # $1: hex-encoded version byte # $2: hex-encoded hash bytes # stdin: none # stdout: the address # stderr: none # return: 0 on success, nonzero on error local version="$1" local hashbytes="$2" echo " c32 = require('c32check'); var btc_version = \"$version\"; var btc_hashbytes = \"$hashbytes\"; var c32_version = 0; if (btc_version === '01') { c32_version = c32.versions.mainnet.p2pkh; } else if (btc_version == '05') { c32_version = c32.versions.mainnet.p2sh; } else if (btc_version == '6f') { c32_version = c32.versions.testnet.p2pkh; } else if (btc_version == 'c4') { c32_version = c32.versions.testnet.p2sh; } else { throw 'Unknown bitcoin address' } var c32addr = c32.c32address(c32_version, btc_hashbytes); console.log(c32.c32ToB58(c32addr)) " | node - 2>/dev/null } decode_scriptSig() { # Decode a scriptSig into its Stacks and Bitcoin addresses # # $1: scriptSig JSON object with .hex and .asm fields # stdin: none # stdout: a JSON object containing the Bitcoin and Stacks addresses # stderr: none # return: 0 on success; 1 if the address could not be decoded local scriptSig="$1" local asm="" local last_asm_field="" local p2sh_json="" local btc_address="" local stx_address="" # if address is given directly, just use it btc_address="$(echo "$scriptSig" | jq -rc '.address')" if [ "$btc_address" = "null" ]; then # verify that we got an asm field that isn't empty asm="$(echo "$scriptSig" | jq -r '.asm')" if [ -z "$asm" ]; then return 0 fi # get last field in the 'asm' representation. # it could be the script of a p2sh, or # it could be the public key of a p2pkh. last_asm_field="$(echo "$asm" | sed -r 's/^.+ ([^ ]+)$/\1/g')" p2sh_json="$(bitcoin_cli decodescript "$last_asm_field")" if [ "$(echo "$p2sh_json" | jq -r '.type')" = "multisig" ]; then # last_asm_field is indeed a multisig script. Extract the Bitcoin address for it. btc_address="$(echo "$p2sh_json" | jq -r '.p2sh')" elif [ "$(echo "$p2sh_json" | jq -r '.type')" = "nonstandard" ]; then # last_asm_field was maybe a public key? It's definitely not a segwit address btc_address="$(pubkey_to_btc_addr "$last_asm_field")" else # segwit -- should have gotten an address passed to us already return 0 fi fi stx_address="$(btc_addr_to_stx_addr "$btc_address")" echo "{\"btc\":\"$btc_address\",\"stx\":\"$stx_address\"}" return 0 } list_stx_voters() { # Enumerate the list of potential voters' STX and BTC addresses, given a vote address # # $1: vote address # stdin: none # stdout: list of newline-separated JSON objects with both BTC and STX addresses of those who voted by sending to this address # stderr: none # return: 0 on success, nonzero on error local vote_addr="$1" local scriptSig="" get_utxos "$vote_addr" | get_scriptSigs | while read -r scriptSig; do decode_scriptSig "$scriptSig" done } is_contract_principal() { # Is a given STX address a contract principal? # # $1: a stx address that is either a standard or contract principal # stdin: none # stdout: "1" if so; "0" if not # stderr: none # return: 0 on success, nonzero on error local addr="$1" if [ -n "$(echo "$addr" | grep -E "\." || true)" ]; then echo "1" else echo "0" fi } stx_addr_to_clarity_principal() { # Convert a STX address into a serialized Clarity principal # # $1: a standard stx address # stdin: none # stdout: a serialized Clarity value representing the principal # stderr: none # return: 0 on success, nonzero on error local stx_addr="$1" if [ "$(is_contract_principal "$stx_addr")" = "1" ]; then # contract principal echo " stxtx = require('@stacks/transactions'); parts = \"$stx_addr\".split('.'); console.log('0x' + stxtx.serializeCV(stxtx.contractPrincipalCV(parts[0], parts[1])).toString('hex')); " | node - else # staandard principal echo " stxtx = require('@stacks/transactions'); console.log('0x' + stxtx.serializeCV(stxtx.standardPrincipalCV(\"$stx_addr\")).toString('hex')); " | node - fi } decode_clarity_value() { # Decode a serialized Clarity value, optionally pulling out a specific field. # Uses @stacks/transactions. # # $1: the clarity hex string to decode # $2: the Javascript path to the data within the decoded value to print # stdin: none # stdout: the data at the end of the decoded value # stderr: none # return: 0 on success, nonzero on error local clarity_hex="$1" local path="$2" echo " stxtx = require('@stacks/transactions'); const decoded = stxtx.deserializeCV(\"$clarity_hex\"); console.log(decoded.$path); " | node - } pox_version_to_btc_version() { # Convert a pox-addr's version byte from .pox into a Bitcoin version byte. # # $1: PoX address version byte # stdin: none # stdout: corresponding BTC version byte # stderr: none # return: 0 on success; 1 on failure local pox_version="$1" if [ "$pox_version" = "00" ]; then if [ $TESTNET -ne 0 ]; then echo "6f" else echo "01" fi elif [ "$pox_version" = "01" ] || [ "$pox_version" = "02" ] || [ "$pox_version" = "03" ]; then if [ $TESTNET -ne 0 ]; then echo "c4" else echo "05" fi else return 1 fi return 0 } is_stacker_delegating() { # Determine if a given stacker is delegating to someone else # # $1: stx address # $2: current burnchain block height # $3: path to the delegating.json file # stdin: none # stdout: "1" if so, "0" if not # stderr: none # return: 0 on success; nonzero on error local stx_addr="$1" local cur_burn_ht="$2" local delegating_file="$3" local json="" local until_ht="" json="$(grep "$stx_addr" "$delegating_file" || true)" if [ -n "$json" ]; then while read -r until_ht; do if (( "$until_ht" >= "$cur_burn_ht" )); then echo "1" return 0 fi done < <(grep "$stx_addr" "$delegating_file" | jq -r -c '.until') echo "0" else echo "0" fi return 0 } get_stacker_info() { # Query the stacks node for a stacker's information - namely, its PoX address and number of uSTX stacked. # # $1: a standard stx address # $2: current burnchain block height # $3: chain tip to use, so as to query historic stacking state # $4: path to delegating.json file # stdin: none # stdout: a JSON object representing the number of uSTX stacked, the STX address, and the PoX address (if this stacker is stacked), or an empty string if not # stderr: none # return: 0 on success, nonzero on error local stx_addr="$1" local cur_burn_ht="$2" local tip="$3" local delegating_file="$4" local body="" local body_len=0 local url="$STX_NODE/v2/contracts/call-read/$POX_ADDR/pox/get-stacker-info" local clarity_value="" local amount_stacked="" local pox_addr_version="" local pox_addr_hashbytes="" local pox_addr="" local delegating=0 if [ -n "$tip" ]; then url="$url?tip=$tip" fi body=" { \"sender\": \"SP31DA6FTSJX2WGTZ69SFY11BH51NZMB0ZW97B5P0.get-info\", \"arguments\": [ \"$(stx_addr_to_clarity_principal "$stx_addr")\" ] } " body_len=${#body} clarity_value="$(echo "$body" | \ curl -s -X POST -H "content-type: application/json" -H "content-length: $body_len" --data-binary @- "$url")" clarity_value="$(echo "$clarity_value" | jq -r -c ".result")" if [ "$clarity_value" = "0x09" ]; then # this is a none echo -n "" else amount_stacked="$(decode_clarity_value "$clarity_value" "value.data['amount-ustx'].value.toString()")" pox_addr_version="$(decode_clarity_value "$clarity_value" "value.data['pox-addr'].data.version.buffer.toString('hex')")" pox_addr_version="$(pox_version_to_btc_version "$pox_addr_version")" pox_addr_hashbytes="$(decode_clarity_value "$clarity_value" "value.data['pox-addr'].data.hashbytes.buffer.toString('hex')")" pox_addr="$(make_btc_addr "$pox_addr_version" "$pox_addr_hashbytes")" # check delegate status delegating="$(is_stacker_delegating "$stx_addr" "$cur_burn_ht" "$delegating_file")" echo "{\"pox_address\":\"$pox_addr\",\"stx_address\":\"$stx_addr\",\"ustx\":\"$amount_stacked\",\"delegating\":\"$delegating\"}" fi } get_stacker_vote() { # Determine how many uSTX a stacker voted with, and whether or not it was "Yes" or "No" # # $1: address mode -- are we querying by STX or BTC address? # $2: the address # $3: path to the 'yes' vote addresses # $4: path to the 'no' vote addresses # $5: path to the stackers.json file # $6: path to list of addresses that voted # stdin: none # stdout: a JSON object describing the stacker address, PoX address, amount voted, vote choice, and which address voted # stderr: none # return: 0 on success, nonzero on error local addr_mode="$1" local select_addr="$2" local vote_path="" local yes_votes_path="$3" local no_votes_path="$4" local stackers_path="$5" local already_voted_path="$6" local stacker_addr="" local pox_addr="" local json="" local vote="" local tmp="" local ustx=0 local voted_ustx=0 local voted_yes=0 local voted_no=0 local delegating=0 local is_pool_pox_addr=0 local stx_btc_addr="" if [ "$addr_mode" = "stx" ]; then stacker_addr="$select_addr" # what was the PoX address for this stacker? json="$(grep -F "$stacker_addr" "$stackers_path" | head -n1 || true)" if [ -z "$json" ]; then # didn't vote return 0 fi pox_addr="$(echo "$json" | jq -r -c '.pox_address' 2>/dev/null || true)" if [ -z "$pox_addr" ]; then # no pox address return 0 fi elif [ "$addr_mode" = "btc" ]; then pox_addr="$select_addr" # what is the first STX address for this PoX addr? json="$(grep -F "$pox_addr" "$stackers_path" | head -n1 || true)" if [ -z "$json" ]; then # didn't vote return 0 fi stacker_addr="$(echo "$json" | jq -r -c '.stx_address' 2>/dev/null || true)" if [ -z "$stacker_addr" ]; then # no STX address return 0 fi # Does this BTC address correspond to a STX address that is stacking? # If not, then it could be a pool address -- i.e. all of its associated # STX addresses are delegating. If that's the case, then unconditionally # count their collective votes, since only the pool's PoX address can vote. for vote_path in "$yes_votes_path" "$no_votes_path"; do tmp="$(grep -F "$pox_addr" "$vote_path" | head -n1 || true)" if [ -n "$tmp" ]; then stx_btc_addr="$(echo "$tmp" | jq -r -c '.stx')" tmp="$(grep -F "$stx_btc_addr" "$stackers_path" | wc -l || true)" if [ "$tmp" = "0" ]; then # all associated STX addresses must be delegating tmp="$(grep -F "$pox_addr" "$stackers_path" | grep -F '"delegating":"0"' | wc -l || true)" if [ "$tmp" = "0" ]; then # all addresses are delegating to this PoX address is_pool_pox_addr=1 fi fi fi done fi if [ $is_pool_pox_addr = 0 ]; then # this isn't a pool PoX address, so don't consider this address's vote if it's delegating delegating="$(echo "$json" | jq -r -c '.delegating')" if [ "$delegating" = "1" ]; then # delegating can't vote return 0 fi fi # must have not already voted touch "$already_voted_path" if [ -n "$(grep -E "$stacker_addr|$pox_addr" "$already_voted_path" || true)" ]; then return 0 fi # voted yes? if [ -n "$(grep -E "$stacker_addr|$pox_addr" "$yes_votes_path" || true)" ]; then voted_yes=1 vote="yes" fi # voted no? if [ -n "$(grep -E "$stacker_addr|$pox_addr" "$no_votes_path" || true)" ]; then voted_no=1 vote="no" fi # must have voted on exactly one choice if [ $voted_yes -eq 0 ] && [ $voted_no -eq 0 ]; then # no vote return 0 fi if [ $voted_yes -eq 1 ] && [ $voted_no -eq 1 ]; then # voted both ways; doesn't count return 0 fi # tally *all* STX bound by this PoX address voted_ustx="$(grep -F "$pox_addr" "$stackers_path" | ( local total_ustx="" while read -r json; do ustx="$(echo "$json" | jq -r -c '.ustx')" if [ -n "$ustx" ]; then total_ustx=$((total_ustx + ustx)) fi done echo "$total_ustx" ) )" # mark as having voted. # be sure to invalidate both the PoX and STX addresses. # if the stacker voted with the PoX address, then invalidate all associated STX addresses too. grep -E "$stacker_addr|$pox_addr" "$yes_votes_path" >> "$already_voted_path" || true grep -E "$stacker_addr|$pox_addr" "$no_votes_path" >> "$already_voted_path" || true echo "{\"pox_address\":\"$pox_addr\",\"vote\":\"$vote\",\"ustx\":\"$voted_ustx\"}" return 0 } tabulate_vote() { # Count up how many votes were received in total for 'yes' and 'no' # # stdin: newline-separated JSON blocks containing '.vote' and '.ustx' # stdout: a JSON object containing 'yes' and 'no' # stderr: none # return: 0 on success, nonzero on error local yes_votes=0 local no_votes=0 local json="" local vote_result="" local vote_ustx="" while IFS= read -r json; do vote_result="$(echo "$json" | jq -r '.vote')" vote_ustx="$(echo "$json" | jq -r '.ustx')" if [[ "$vote_result" = "yes" ]]; then yes_votes=$((yes_votes + vote_ustx)) elif [[ "$vote_result" = "no" ]]; then no_votes=$((no_votes + vote_ustx)) else echo >&2 "Invalid vote in \"$json\"" fi done echo "{\"yes\":\"$yes_votes\",\"no\":\"$no_votes\"}" } deps_check() { # Make sure all binary dependencies are available. # # stdin: none # stdout: none # stderr: none # return: 0 on success; exits on failure for cmd in bitcoin-cli jq grep date xargs node; do if ! command -v "$cmd" >/dev/null 2>&1; then echo >&2 "FATAL: could not find \"$cmd\" in PATH" exit 1 fi done # make sure node packages are available if ! echo " const c32 = require('c32check'); const btc = require('bitcoinjs-lib'); const stx = require('@stacks/transactions'); " | node; then echo >&2 "FATAL: missing node packages c32check, bitcoinjs-lib, and/or @stacks/transactions" exit 1 fi return 0 } main() { # Main entry point of the script. Tabulates a "yes/no" vote for the reward cycle that is currently # ongoing as of a given chain tip hash. # # $1: working directory to use # $2: chaintip of vote tally block # stdin: a list of JSON stacker records that describe either stacking or delegating # stdout: a JSON object with the sums of the "yes" and "no" votes # stderr: none # return: 0 on success, nonzero on error local work_dir="$1" local tip="$2" local cur_burn_ht="$3" local stacker_addrs_path="$work_dir/input.txt" local yes_votes_path="$work_dir/yes-addrs.json" local no_votes_path="$work_dir/no-addrs.json" local stacker_path="$work_dir/stackers.json" local votes_path="$work_dir/votes.json" local all_votes_path="$work_dir/.all-votes.json" local already_voted_path="$work_dir/.already_voted.json" local delegates="$work_dir/delegating.json" local vote_json="" local btc_addr="" local stx_addr="" local stacker_json="" local record_type="" deps_check mkdir -p "$work_dir" echo -n "" > "$stacker_addrs_path" echo -n "" > "$votes_path" echo -n "" > "$already_voted_path" echo -n "" > "$delegates" # copy stdin to a file so we can reuse it while read -r stacker_json; do record_type="$(echo "$stacker_json" | jq -r -c '.type')" if [ "$record_type" = "delegation" ]; then echo "$stacker_json" >> "$delegates" elif [ "$record_type" = "stacker" ]; then echo "$stacker_json" | jq -r -c '.address' >> "$stacker_addrs_path" else echo >&2 "Invalid record $stacker_json" exit 1 fi done if ! [ -f "$yes_votes_path" ]; then echo >&2 "Obtaining 'yes' votes..." list_stx_voters "$YES_ADDR" > "$yes_votes_path" fi if ! [ -f "$no_votes_path" ]; then echo >&2 "Obtaining 'no' votes..." list_stx_voters "$NO_ADDR" > "$no_votes_path" fi if ! [ -f "$stacker_path" ]; then echo >&2 "Obtaining stacker info as of $tip..." while read -r stx_addr; do get_stacker_info "$stx_addr" "$cur_burn_ht" "$tip" "$delegates" >> "$stacker_path" done < "$stacker_addrs_path" else echo >&2 "Stacker consistency check as of $tip..." while read -r stx_addr; do json="$(grep "$stx_addr" "$stacker_path" || true)" if [ -z "$json" ]; then get_stacker_info "$stx_addr" "$cur_burn_ht" "$tip" "$delegates" >> "$stacker_path" fi done < "$stacker_addrs_path" fi # get each stacker's vote. # first, consider all voting transactions' BTC addresses, since these could be PoX addresses, and could count for many Stackers. # second, consider all voting transactions' STX addresses, since the ones that haven't voted now represent Stackers who voted from their Stacking address cat "$yes_votes_path" "$no_votes_path" > "$all_votes_path" while read -r vote_json; do btc_addr="$(echo "$vote_json" | jq -r -c '.btc')" get_stacker_vote "btc" "$btc_addr" "$yes_votes_path" "$no_votes_path" "$stacker_path" "$already_voted_path" >> "$votes_path" done < "$all_votes_path" while read -r vote_json; do stx_addr="$(echo "$vote_json" | jq -r -c '.stx')" get_stacker_vote "stx" "$stx_addr" "$yes_votes_path" "$no_votes_path" "$stacker_path" "$already_voted_path" >> "$votes_path" done < "$all_votes_path" # tabulate votes tabulate_vote < "$votes_path" } ############################################################### # # Unit tests # ############################################################### assert_eq() { if [[ "$1" != "$2" ]]; then echo "" echo >&2 "left: \"$1\"" echo >&2 "right: \"$2\"" exit 1 fi } test_btc_addr_to_stx_addr() { echo -n "test_btc_addr_to_stx_addr..." assert_eq "SP3M89DCQMWX7ZQF588H0EP8TYWG1AWMF3DAG30VH" "$(btc_addr_to_stx_addr "1NCSkK5HovkD4Bq8pLertRN4U7CJnZjJk2")" assert_eq "ST3M89DCQMWX7ZQF588H0EP8TYWG1AWMF3D5DZR06" "$(btc_addr_to_stx_addr "n2iQ3NAGcxBTqJJkXudEiLaPL6o1kjaUBU")" echo "ok" } test_pubkey_to_btc_addr() { echo -n "test_pubkey_to_btc_addr..." assert_eq "1NCSkK5HovkD4Bq8pLertRN4U7CJnZjJk2" "$(pubkey_to_btc_addr "020c49a083d703a4db64e4e26ad5f3ab1ae2def84fdd902e99a4bd7bdcbc2b35fc")" assert_eq "1Mz2nSAg6qYTKxxnbfgJ8Snt6WtEJK3u7j" "$(pubkey_to_btc_addr "040c49a083d703a4db64e4e26ad5f3ab1ae2def84fdd902e99a4bd7bdcbc2b35fc8fa79a7a4e262f66b9482b1f3ce20444af8bf32449a981e4d9e2b52502bdd354")" echo "ok" } test_make_btc_addr() { echo -n "test_make_btc_addr..." assert_eq "1J9pXQrKMDNkRJWbDTSiEASiL76GeWKVck" "$(make_btc_addr "01" "bc25232837a04fd94a491cf2bbc51f59ed80d1dd")" assert_eq "3JqqSxLku7h8WUD2LZ7JenoeUdNzBNEDH6" "$(make_btc_addr "05" "bc25232837a04fd94a491cf2bbc51f59ed80d1dd")" assert_eq "mxfmpTwJAEp1CQzCw2R645f3C6gydDtwxS" "$(make_btc_addr "6f" "bc25232837a04fd94a491cf2bbc51f59ed80d1dd")" assert_eq "2NAQ3WhGnWaCUiFqa1gjBGjnugyb9zA1DRo" "$(make_btc_addr "c4" "bc25232837a04fd94a491cf2bbc51f59ed80d1dd")" echo "ok" } test_get_scriptSig() { echo -n "test_get_scriptSig..." # segwit-over-p2sh assert_eq '{"hex": "00", "asm": "00", "address": "3HASeAxLLZQJTbGDTSyfxbJXLtnUF8K1Dw"}' "$(echo '[{"txid": "13c2d9af1bc99b96e4c03f428be7580eeb1d745980a9e946dece7947c153e81f"}]' | get_scriptSigs)" # p2pkh assert_eq '{"asm":"304402206d333b3ced3f75d1de0cd89450231b2a636965a8a9bc457115f48d34b63c917a022079f49c2cea06ced80e871b55e009b30585de92302fda820c3ae37b596d3d75d2[ALL] 02cd8737f57117705588c8ab282664e76e93217f8c953653905edcba2bc1be1ae9","hex":"47304402206d333b3ced3f75d1de0cd89450231b2a636965a8a9bc457115f48d34b63c917a022079f49c2cea06ced80e871b55e009b30585de92302fda820c3ae37b596d3d75d2012102cd8737f57117705588c8ab282664e76e93217f8c953653905edcba2bc1be1ae9"}' "$(echo '[{"txid": "9412c01065dedaa5e4767aca5e6e6f0b1e542147823d6428f6bbb418e6c33e57"}]' | get_scriptSigs)" echo "ok" } test_decode_scriptSig() { echo -n "test_decode_scriptSig..." local json="" json="$(decode_scriptSig '{"asm":"0 304402206af58d064c5cfb4128367d3fb7393527117bc98166fe6c736e84b513be9933ad02204b3da3d246b2e1d72dc993ccfb09626c3b066d7e43bcccc24e8f376fccb6bc06[ALL] 3045022100ba8ce4d5d4a568f50f3cf7868d9dce687f1dbacfc98b8ea4c66722be6e35654a0220147da24ca66f553b39627ace2be658701ba36a42fc725565c79a608a37197113[ALL] 5221031e5b50460922bfcea36dda0d45452af7b743e96ba01150d24d890f1878abd0d52103ff1f5fcb32a17fb645887180364a864df3e442ab838330dbd72f93199de305be52ae","hex":"0047304402206af58d064c5cfb4128367d3fb7393527117bc98166fe6c736e84b513be9933ad02204b3da3d246b2e1d72dc993ccfb09626c3b066d7e43bcccc24e8f376fccb6bc0601483045022100ba8ce4d5d4a568f50f3cf7868d9dce687f1dbacfc98b8ea4c66722be6e35654a0220147da24ca66f553b39627ace2be658701ba36a42fc725565c79a608a3719711301475221031e5b50460922bfcea36dda0d45452af7b743e96ba01150d24d890f1878abd0d52103ff1f5fcb32a17fb645887180364a864df3e442ab838330dbd72f93199de305be52ae"}')" assert_eq "38CCKFZqyTZVx8DFUsBWKsoB4zLUVQ3H9Z" "$(echo "$json" | jq -r -c '.btc')" assert_eq "SM13NAZFKN7YDX7Z2S9V89YQ835BZ27FPVXYDH7GE" "$(echo "$json" | jq -r -c '.stx')" json="$(decode_scriptSig '{"asm": "3045022100e224ce381ae121cb59bcdbab86e2a06f9c2f288558dd4771bec72947bf2340b602204dcf2ba336b9209ae86d7468c5172b0459b004c66923e68fcb6d3f08c318a524[ALL] 035180521b9c167493d41ea30e7c2dc9c35b2a003d39317a20ccde837fb268b8df", "hex": "483045022100e224ce381ae121cb59bcdbab86e2a06f9c2f288558dd4771bec72947bf2340b602204dcf2ba336b9209ae86d7468c5172b0459b004c66923e68fcb6d3f08c318a5240121035180521b9c167493d41ea30e7c2dc9c35b2a003d39317a20ccde837fb268b8df"}')" assert_eq "16sAXi1jxhxKCfY84hubDdnFNaAhqd5t49" "$(echo "$json" | jq -r -c '.btc')" assert_eq "SP105ARDW7EQTFTFMYNGMKJTA9JYFHF0FFMW9K815" "$(echo "$json" | jq -r -c '.stx')" echo "ok" } test_stx_addr_to_clarity_principal() { echo -n "test_stx_addr_to_clarity_principal..." assert_eq "0x0516c8b2df71cedcdf5f32dcf2228976b0e7f5ba7231" "$(stx_addr_to_clarity_principal "SP34B5QVHSVEDYQSJVKS252BPP3KZBEKJ67SAYQCD")" assert_eq "0x051447557df3a9fcde9fe2ca7684fae81957f11df6df" "$(stx_addr_to_clarity_principal "SM13NAZFKN7YDX7Z2S9V89YQ835BZ27FPVXYDH7GE")" assert_eq "0x06160b4e114c8d7c79497cd8447f28f5ebdcfc71648703666f6f" "$(stx_addr_to_clarity_principal "SP5MW4ACHNY7JJBWV127YA7NXFEFRWB4GYPWFGTN.foo")" echo "ok" } test_decode_clarity_value() { echo -n "test_decode_clarity_value..." assert_eq "12291711000000n" "$(decode_clarity_value "0x0a0c000000040b616d6f756e742d7573747801000000000000000000000b2de3115dc01266697273742d7265776172642d6379636c65010000000000000000000000000000000f0b6c6f636b2d706572696f64010000000000000000000000000000000c08706f782d616464720c00000002096861736862797465730200000014bc25232837a04fd94a491cf2bbc51f59ed80d1dd0776657273696f6e020000000101" "value.data['amount-ustx'].value")" assert_eq "01" "$(decode_clarity_value "0x0a0c000000040b616d6f756e742d7573747801000000000000000000000b2de3115dc01266697273742d7265776172642d6379636c65010000000000000000000000000000000f0b6c6f636b2d706572696f64010000000000000000000000000000000c08706f782d616464720c00000002096861736862797465730200000014bc25232837a04fd94a491cf2bbc51f59ed80d1dd0776657273696f6e020000000101" "value.data['pox-addr'].data.version.buffer.toString('hex')")" assert_eq "bc25232837a04fd94a491cf2bbc51f59ed80d1dd" "$(decode_clarity_value "0x0a0c000000040b616d6f756e742d7573747801000000000000000000000b2de3115dc01266697273742d7265776172642d6379636c65010000000000000000000000000000000f0b6c6f636b2d706572696f64010000000000000000000000000000000c08706f782d616464720c00000002096861736862797465730200000014bc25232837a04fd94a491cf2bbc51f59ed80d1dd0776657273696f6e020000000101" "value.data['pox-addr'].data.hashbytes.buffer.toString('hex')")" echo "ok" } test_bitcoin_ping() { echo -n "test_bitcoin_ping..." bitcoin_cli ping echo "ok" } test_is_stacker_delegating() { echo -n "test_is_stacker_delegating..." local test_dir="/tmp/sip012-vote-count/test/test_is_stacker_delegating" if [ -d "$test_dir" ]; then mv "$test_dir" "$test_dir.bak.$(date +%s)" fi mkdir -p "$test_dir" echo '{"address":"SP1H5X1MP2DQ9KXHZFVC3E43NNTZRCJXKMHCAE8N","until":"710150"}' > "$test_dir/delegating.json" local ret="" ret="$(is_stacker_delegating "SP1H5X1MP2DQ9KXHZFVC3E43NNTZRCJXKMHCAE8N" "700000" "$test_dir/delegating.json")" assert_eq "1" "$ret" ret="$(is_stacker_delegating "SP1H5X1MP2DQ9KXHZFVC3E43NNTZRCJXKMHCAE8N" "710150" "$test_dir/delegating.json")" assert_eq "1" "$ret" ret="$(is_stacker_delegating "SP1H5X1MP2DQ9KXHZFVC3E43NNTZRCJXKMHCAE8N" "710151" "$test_dir/delegating.json")" assert_eq "0" "$ret" ret="$(is_stacker_delegating "SM21NR8W96KGG4YCFXRHZ6EAR2PAY3NS0MQ3FK95T" "700000" "$test_dir/delegating.json")" assert_eq "0" "$ret" echo "ok" } test_get_stacker_info() { echo -n "test_get_stacker_info..." local test_dir="/tmp/sip012-vote-count/test/test_get_stacker_info" if [ -d "$test_dir" ]; then mv "$test_dir" "$test_dir.bak.$(date +%s)" fi mkdir -p "$test_dir" local json="" local cur_burn_ht=708236 local tip="22ac907019619fe9ae4e4ef5100740ecbf1b95510caccfb59fb71053e85bd783" local contract_tip="7ae943351df455aab1aa69ce7ba6606f937ebab5f34322c982227cd9e0322176" local delegates="$test_dir/delegates.json" echo '{"address":"SPRPW92SBDC982QJDPHAAXN3DGKV798CRM0ZWX63","until":"712250"}' > "$delegates" # multisig json="$(get_stacker_info "SM21NR8W96KGG4YCFXRHZ6EAR2PAY3NS0MQ3FK95T" "$cur_burn_ht" "$tip" "$delegates")" assert_eq "SM21NR8W96KGG4YCFXRHZ6EAR2PAY3NS0MQ3FK95T" "$(echo "$json" | jq -r -c '.stx_address')" assert_eq "3JqqSxLku7h8WUD2LZ7JenoeUdNzBNEDH6" "$(echo "$json" | jq -r -c '.pox_address')" assert_eq "12291711000000" "$(echo "$json" | jq -r -c '.ustx')" assert_eq "0" "$(echo "$json" | jq -r -c '.delegating')" # singlesig json="$(get_stacker_info "SM12TJXJEQQER0EWX6783RWH1R8YZG3M9SBQVDFH" "$cur_burn_ht" "$tip" "$delegates")" assert_eq "SM12TJXJEQQER0EWX6783RWH1R8YZG3M9SBQVDFH" "$(echo "$json" | jq -r -c '.stx_address')" assert_eq "1AvrGwwtm5acsbVEaeHk7FkAbQh2FJgfSn" "$(echo "$json" | jq -r -c '.pox_address')" assert_eq "15000000000000" "$(echo "$json" | jq -r -c '.ustx')" assert_eq "0" "$(echo "$json" | jq -r -c '.delegating')" # contract json="$(get_stacker_info "SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR.arkadiko-stacker-v1-1" "$cur_burn_ht" "$contract_tip" "$delegates")" assert_eq "SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR.arkadiko-stacker-v1-1" "$(echo "$json" | jq -r -c '.stx_address')" assert_eq "1EsgJoqwcfbWsSyz6VHCYBvnh1iGrWB7dY" "$(echo "$json" | jq -r -c '.pox_address')" assert_eq "12622260119595" "$(echo "$json" | jq -r -c '.ustx')" assert_eq "0" "$(echo "$json" | jq -r -c '.delegating')" # someone who isn't stacking json="$(get_stacker_info "SP2E8N3T3TJP2D9YQZ41PY7X0ZFNQA8PZZ9RES24G" "$cur_burn_ht" "$tip" "$delegates")" assert_eq "" "$json" # someone who delegated json="$(get_stacker_info "SPRPW92SBDC982QJDPHAAXN3DGKV798CRM0ZWX63" "$cur_burn_ht" "$tip" "$delegates")" assert_eq "SPRPW92SBDC982QJDPHAAXN3DGKV798CRM0ZWX63" "$(echo "$json" | jq -r -c '.stx_address')" assert_eq '14y4Z27pvL6Fyo4WawkYGRBmwtg3rzTGrq' "$(echo "$json" | jq -r -c '.pox_address')" assert_eq "50000000000" "$(echo "$json" | jq -r -c '.ustx')" assert_eq "1" "$(echo "$json" | jq -r -c '.delegating')" echo "ok" } test_get_stacker_vote() { echo -n "test_get_stacker_vote..." local json="" local test_dir="/tmp/sip012-vote-count/test/test_get_stacker_vote" if [ -d "$test_dir" ]; then mv "$test_dir" "$test_dir.bak.$(date +%s)" fi mkdir -p "$test_dir" # mock yes votes cat > "$test_dir/yes.txt" <<EOF {"btc":"1PDKpmhYBVrv4XTBHZ2rGkuwweysNvom5R","stx":"SP3STEY6JEYREF8Y023T82740V5B6DBE7QBCR4FMK"} {"btc":"18MnxXFqf8kt4exiZcU6RMLEcuyZP9w4rP","stx":"SP18BF40VF19V5YM4GCZCZH5G0P95963BHEGCCSB1"} {"btc":"19qtkM3DsLD6di4AxFF3UQhsCqdeGMeeRS","stx":"SP1GG0ASZE7BJDJDDEEBYNM3FGZWZHW7MXN87NBY5"} {"btc":"161AHK9MADzbjtq9KmgQDV5yFztVmpM4At","stx":"SP2S1TVYFQV12RDNAT5JKC0NB0Y58TYCV2SRP1H4H"} {"btc":"12mb6aPVSJUNA6KxBLAKTKL8dXgn9y1XMZ","stx":"SP9PDRYYX44THVQM2BG214ZDAQ6ZAG380BXJN0J8"} {"btc":"1E3zCmpSSy5MFPWe8YCUuS7kYua5yDMPp7","stx":"SP27JMDXYQ79WWZ7QVJ99NZ0QPGF8VY45NDCVTXPY"} {"btc":"1LhPVRPowv9fDzzpKg3ku4GQrAq5HpChFE","stx":"SP3C0VKAMZTBKYDEBG0BGSCS64EM425N2PN7QQZ8A"} {"btc":"1LhPVRPowv9fDzzpKg3ku4GQrAq5HpChFE","stx":"SP3QXB26B01E5P6WZN7KAMFDHBCSPKTKR8X6RZ3M9"} EOF # mock no votes cat > "$test_dir/no.txt" <<EOF {"btc":"1LjDBXmKw5wToyrEaWR2eDUXTkEwak9iZq","stx":"SP3C6C1V8M20ZH035TBQSG584DBHTFQ96E0TYSXWX"} {"btc":"1MLb822dHgS1isLav6wL1wAQDhVPF8KcKV","stx":"SP3FHDSPQ837S9PJ24FNRJD4W98TYR58PSVYGB3HN"} {"btc":"131hPM4nFpfoxm1FXCjq9VuqS1yoxGCWUL","stx":"SPB13XN7RHHST7RDNQCM0HGN7EDT9NEFA85P719W"} {"btc":"1HEnYSz7bT8WgzBnnJHbbNoiXRNwCDV8tL","stx":"SP2S1TVYFQV12RDNAT5JKC0NB0Y58TYCV2SRP1H4H"} {"btc":"12mb6aPVSJUNA6KxBLAKTKL8dXgn9y1XMZ","stx":"SP2TTHFH4FS6TRDEAZPQBCSRY1P6XFWWTNS2FV888"} EOF # mock stackers # #1 voted with their STX address # #2 voted with their BTC address # #3 didn't vote # #4 is #1 but voting with their BTC address # #5 is #2 but voting with their STX address # #6 voted twice, via their STX address # #7 voted twice, via their BTC address # #8 and #9 are pooling with the same PoX address # #10 delegates cat > "$test_dir/stackers.json" <<EOF {"stx_address": "SP3STEY6JEYREF8Y023T82740V5B6DBE7QBCR4FMK", "pox_address": "1JqvPcVGsv5HxKwRnrpJizmTrihgmtSztk", "ustx": "200", "delegating": "0"} {"stx_address": "SP35N8ZBAANK3886HHEDJ6MMXEM58VJENHWH80PD5", "pox_address": "1LjDBXmKw5wToyrEaWR2eDUXTkEwak9iZq", "ustx": "100", "delegating": "0"} {"stx_address": "SP30H7BJWABGSMTS5F5NTVTBVKZPFCDB218PPS9HK", "pox_address": "15NqmwLcQ9TX136A2ejRQzt3LoL5H8WwzH", "ustx": "600", "delegating": "0"} {"stx_address": "SP30YGE0QD3H8S8R6JKWPH0VKJY5WCRKCZDZ3FPE1", "pox_address": "1PDKpmhYBVrv4XTBHZ2rGkuwweysNvom5R", "ustx": "3", "delegating": "0"} {"stx_address": "SP3STEY6JEYREF8Y023T82740V5B6DBE7QBCR4FMK", "pox_address": "16ecPjRJgaiEv81DcVewZm1qchkUrXtfk7", "ustx": "200", "delegating": "0"} {"stx_address": "SP2S1TVYFQV12RDNAT5JKC0NB0Y58TYCV2SRP1H4H", "pox_address": "1MD2VpS3kxDNTcPjARZPXPJTYeucVD3nm6", "ustx": "5", "delegating": "0"} {"stx_address": "SP35AF3G0M2GFA34S8R3VT0PRHQW563D4FH80JMCH", "pox_address": "12mb6aPVSJUNA6KxBLAKTKL8dXgn9y1XMZ", "ustx": "6", "delegating": "0"} {"stx_address": "SP3QXB26B01E5P6WZN7KAMFDHBCSPKTKR8X6RZ3M9", "pox_address": "1LhPVRPowv9fDzzpKg3ku4GQrAq5HpChFE", "ustx": "1000", "delegating": "0"} {"stx_address": "SP3C0VKAMZTBKYDEBG0BGSCS64EM425N2PN7QQZ8A", "pox_address": "1LhPVRPowv9fDzzpKg3ku4GQrAq5HpChFE", "ustx": "3000", "delegating": "0"} {"stx_address": "SP3RV0668H0F1WF9ZX290BEVK79AMK8KSPPQVNDPP", "pox_address": "1LhPVRPowv9fDzzpKg3ku4GQrAq5HpChFE", "ustx": "1", "delegating": "1"} EOF # mock already-voted touch "$test_dir/.already-voted.json" # voter #1 -- voted with STX address json="$(get_stacker_vote "stx" "SP3STEY6JEYREF8Y023T82740V5B6DBE7QBCR4FMK" "$test_dir/yes.txt" "$test_dir/no.txt" "$test_dir/stackers.json" "$test_dir/.already-voted.json")" assert_eq "yes" "$(echo "$json" | jq -r -c '.vote')" assert_eq "200" "$(echo "$json" | jq -r -c '.ustx')" # voter #2 -- voted with PoX address json="$(get_stacker_vote "stx" "SP35N8ZBAANK3886HHEDJ6MMXEM58VJENHWH80PD5" "$test_dir/yes.txt" "$test_dir/no.txt" "$test_dir/stackers.json" "$test_dir/.already-voted.json")" assert_eq "no" "$(echo "$json" | jq -r -c '.vote')" assert_eq "100" "$(echo "$json" | jq -r -c '.ustx')" # voter #3 -- didn't vote json="$(get_stacker_vote "stx" "SP30H7BJWABGSMTS5F5NTVTBVKZPFCDB218PPS9HK" "$test_dir/yes.txt" "$test_dir/no.txt" "$test_dir/stackers.json" "$test_dir/.already-voted.json")" assert_eq "" "$json" # voter #4 -- voted already via PoX address json="$(get_stacker_vote "stx" "SP30YGE0QD3H8S8R6JKWPH0VKJY5WCRKCZDZ3FPE1" "$test_dir/yes.txt" "$test_dir/no.txt" "$test_dir/stackers.json" "$test_dir/.already-voted.json")" assert_eq "" "$json" # voter #5 -- voted already via STX address json="$(get_stacker_vote "stx" "SP3STEY6JEYREF8Y023T82740V5B6DBE7QBCR4FMK" "$test_dir/yes.txt" "$test_dir/no.txt" "$test_dir/stackers.json" "$test_dir/.already-voted.json")" assert_eq "" "$json" # voter #6 -- voted both ways via STX json="$(get_stacker_vote "stx" "SP2S1TVYFQV12RDNAT5JKC0NB0Y58TYCV2SRP1H4H" "$test_dir/yes.txt" "$test_dir/no.txt" "$test_dir/stackers.json" "$test_dir/.already-voted.json")" assert_eq "" "$json" # voter #7 -- voted both ways via BTC json="$(get_stacker_vote "stx" "SP35AF3G0M2GFA34S8R3VT0PRHQW563D4FH80JMCH" "$test_dir/yes.txt" "$test_dir/no.txt" "$test_dir/stackers.json" "$test_dir/.already-voted.json")" assert_eq "" "$json" # voter #8 and #9 are pooling # voter #10 is pooling with them but delegating json="$(get_stacker_vote "stx" "SP3C0VKAMZTBKYDEBG0BGSCS64EM425N2PN7QQZ8A" "$test_dir/yes.txt" "$test_dir/no.txt" "$test_dir/stackers.json" "$test_dir/.already-voted.json")" assert_eq "yes" "$(echo "$json" | jq -r -c '.vote')" assert_eq "4001" "$(echo "$json" | jq -r -c '.ustx')" json="$(get_stacker_vote "stx" "SP3QXB26B01E5P6WZN7KAMFDHBCSPKTKR8X6RZ3M9" "$test_dir/yes.txt" "$test_dir/no.txt" "$test_dir/stackers.json" "$test_dir/.already-voted.json")" assert_eq "" "$json" # do it again but with BTC rm "$test_dir/.already-voted.json" # voter #1 -- voted with PoX address json="$(get_stacker_vote "btc" "1PDKpmhYBVrv4XTBHZ2rGkuwweysNvom5R" "$test_dir/yes.txt" "$test_dir/no.txt" "$test_dir/stackers.json" "$test_dir/.already-voted.json")" assert_eq "yes" "$(echo "$json" | jq -r -c '.vote')" assert_eq "3" "$(echo "$json" | jq -r -c '.ustx')" # voter #2 -- voted with PoX address json="$(get_stacker_vote "btc" "1LjDBXmKw5wToyrEaWR2eDUXTkEwak9iZq" "$test_dir/yes.txt" "$test_dir/no.txt" "$test_dir/stackers.json" "$test_dir/.already-voted.json")" assert_eq "no" "$(echo "$json" | jq -r -c '.vote')" assert_eq "100" "$(echo "$json" | jq -r -c '.ustx')" # voter #3 -- didn't vote json="$(get_stacker_vote "btc" "15NqmwLcQ9TX136A2ejRQzt3LoL5H8WwzH" "$test_dir/yes.txt" "$test_dir/no.txt" "$test_dir/stackers.json" "$test_dir/.already-voted.json")" assert_eq "" "$json" # voter #4 -- voted already via PoX address json="$(get_stacker_vote "stx" "SP30YGE0QD3H8S8R6JKWPH0VKJY5WCRKCZDZ3FPE1" "$test_dir/yes.txt" "$test_dir/no.txt" "$test_dir/stackers.json" "$test_dir/.already-voted.json")" assert_eq "" "$json" # already voted via PoX address (#1) json="$(get_stacker_vote "btc" "1PDKpmhYBVrv4XTBHZ2rGkuwweysNvom5R" "$test_dir/yes.txt" "$test_dir/no.txt" "$test_dir/stackers.json" "$test_dir/.already-voted.json")" assert_eq "" "$json" # voter #5 -- voted already via PoX address json="$(get_stacker_vote "stx" "SP3STEY6JEYREF8Y023T82740V5B6DBE7QBCR4FMK" "$test_dir/yes.txt" "$test_dir/no.txt" "$test_dir/stackers.json" "$test_dir/.already-voted.json")" assert_eq "" "$json" # already voted via PoX address (the above STX address) json="$(get_stacker_vote "btc" "1JqvPcVGsv5HxKwRnrpJizmTrihgmtSztk" "$test_dir/yes.txt" "$test_dir/no.txt" "$test_dir/stackers.json" "$test_dir/.already-voted.json")" assert_eq "" "$json" # voter #6 -- voted both ways via STX json="$(get_stacker_vote "btc" "1HEnYSz7bT8WgzBnnJHbbNoiXRNwCDV8tL" "$test_dir/yes.txt" "$test_dir/no.txt" "$test_dir/stackers.json" "$test_dir/.already-voted.json")" assert_eq "" "$json" # voter #6 -- voted both ways via STX json="$(get_stacker_vote "btc" "1MD2VpS3kxDNTcPjARZPXPJTYeucVD3nm6" "$test_dir/yes.txt" "$test_dir/no.txt" "$test_dir/stackers.json" "$test_dir/.already-voted.json")" assert_eq "" "$json" # voter #7 -- voted both ways via BTC json="$(get_stacker_vote "btc" "12mb6aPVSJUNA6KxBLAKTKL8dXgn9y1XMZ" "$test_dir/yes.txt" "$test_dir/no.txt" "$test_dir/stackers.json" "$test_dir/.already-voted.json")" assert_eq "" "$json" # voter #11 is delegating, so it can't vote json="$(get_stacker_vote "stx" "SP3RV0668H0F1WF9ZX290BEVK79AMK8KSPPQVNDPP" "$test_dir/yes.txt" "$test_dir/no.txt" "$test_dir/stackers.json" "$test_dir/.already-voted.json")" assert_eq "" "$json" # voter #8 and #9 are pooling, and #10 delegates json="$(get_stacker_vote "btc" "1LhPVRPowv9fDzzpKg3ku4GQrAq5HpChFE" "$test_dir/yes.txt" "$test_dir/no.txt" "$test_dir/stackers.json" "$test_dir/.already-voted.json")" assert_eq "yes" "$(echo "$json" | jq -r -c '.vote')" assert_eq "4001" "$(echo "$json" | jq -r -c '.ustx')" json="$(get_stacker_vote "btc" "1LhPVRPowv9fDzzpKg3ku4GQrAq5HpChFE" "$test_dir/yes.txt" "$test_dir/no.txt" "$test_dir/stackers.json" "$test_dir/.already-voted.json")" assert_eq "" "$json" echo "ok" } test_tabulate_vote() { echo -n "test_tabulate_vote..." local json="" json="$(printf "{\"ustx\":\"100\",\"vote\":\"yes\"}\n{\"ustx\":\"200\",\"vote\":\"yes\"}\n{\"ustx\":\"10\",\"vote\":\"no\"}\n{\"ustx\":\"20\",\"vote\":\"no\"}\n{\"ustx\":\"1000\",\"vote\":\"nope\"}\n" | tabulate_vote 2>/dev/null)" assert_eq "300" "$(echo "$json" | jq -r -c '.yes')" assert_eq "30" "$(echo "$json" | jq -r -c '.no')" echo "ok" } test_main() { echo -n "test_main..." local json="" local result="" local test_dir="/tmp/sip012-vote-count/test/test_main" local tip="22ac907019619fe9ae4e4ef5100740ecbf1b95510caccfb59fb71053e85bd783" local cur_burn_ht=707148 if [ -d "$test_dir" ]; then mv "$test_dir" "$test_dir.bak.$(date +%s)" fi mkdir -p "$test_dir" # mock yes votes cat > "$test_dir/yes-addrs.json" <<EOF {"btc":"1PDKpmhYBVrv4XTBHZ2rGkuwweysNvom5R","stx":"SP3STEY6JEYREF8Y023T82740V5B6DBE7QBCR4FMK"} {"btc":"18MnxXFqf8kt4exiZcU6RMLEcuyZP9w4rP","stx":"SP18BF40VF19V5YM4GCZCZH5G0P95963BHEGCCSB1"} {"btc":"19qtkM3DsLD6di4AxFF3UQhsCqdeGMeeRS","stx":"SP1GG0ASZE7BJDJDDEEBYNM3FGZWZHW7MXN87NBY5"} {"btc":"161AHK9MADzbjtq9KmgQDV5yFztVmpM4At","stx":"SP2S1TVYFQV12RDNAT5JKC0NB0Y58TYCV2SRP1H4H"} {"btc":"12mb6aPVSJUNA6KxBLAKTKL8dXgn9y1XMZ","stx":"SP9PDRYYX44THVQM2BG214ZDAQ6ZAG380BXJN0J8"} {"btc":"1E3zCmpSSy5MFPWe8YCUuS7kYua5yDMPp7","stx":"SP27JMDXYQ79WWZ7QVJ99NZ0QPGF8VY45NDCVTXPY"} {"btc":"1LhPVRPowv9fDzzpKg3ku4GQrAq5HpChFE","stx":"SP3C0VKAMZTBKYDEBG0BGSCS64EM425N2PN7QQZ8A"} {"btc":"1LhPVRPowv9fDzzpKg3ku4GQrAq5HpChFE","stx":"SP3QXB26B01E5P6WZN7KAMFDHBCSPKTKR8X6RZ3M9"} EOF # mock no votes cat > "$test_dir/no-addrs.json" <<EOF {"btc":"1LjDBXmKw5wToyrEaWR2eDUXTkEwak9iZq","stx":"SP3C6C1V8M20ZH035TBQSG584DBHTFQ96E0TYSXWX"} {"btc":"1MLb822dHgS1isLav6wL1wAQDhVPF8KcKV","stx":"SP3FHDSPQ837S9PJ24FNRJD4W98TYR58PSVYGB3HN"} {"btc":"131hPM4nFpfoxm1FXCjq9VuqS1yoxGCWUL","stx":"SPB13XN7RHHST7RDNQCM0HGN7EDT9NEFA85P719W"} {"btc":"1HEnYSz7bT8WgzBnnJHbbNoiXRNwCDV8tL","stx":"SP2S1TVYFQV12RDNAT5JKC0NB0Y58TYCV2SRP1H4H"} {"btc":"12mb6aPVSJUNA6KxBLAKTKL8dXgn9y1XMZ","stx":"SP2TTHFH4FS6TRDEAZPQBCSRY1P6XFWWTNS2FV888"} EOF # mock stackers # #1 voted with their STX address, but because we count BTC addresses first, this one won't get counted. # #2 voted with their BTC address, and gets counted # #3 didn't vote # #4 is #1 but voting with their BTC address. Because BTC gets counted first, its vote counts (not the one with the STX vote). # #5 is #2 but voting with their STX address. Because the BTC vote got counted earlier, this vote won't count. # #6 voted twice, via their STX address, and won't count # #7 voted twice, via their BTC address, and won't count # #8 and #9 are pooling with the same PoX address, and will count as a single unit # #10 delegates cat > "$test_dir/stackers.json" <<EOF {"stx_address": "SP3STEY6JEYREF8Y023T82740V5B6DBE7QBCR4FMK", "pox_address": "1JqvPcVGsv5HxKwRnrpJizmTrihgmtSztk", "ustx": "200", "delegating": "0"} {"stx_address": "SP35N8ZBAANK3886HHEDJ6MMXEM58VJENHWH80PD5", "pox_address": "1LjDBXmKw5wToyrEaWR2eDUXTkEwak9iZq", "ustx": "100", "delegating": "0"} {"stx_address": "SP30H7BJWABGSMTS5F5NTVTBVKZPFCDB218PPS9HK", "pox_address": "15NqmwLcQ9TX136A2ejRQzt3LoL5H8WwzH", "ustx": "600", "delegating": "0"} {"stx_address": "SP30YGE0QD3H8S8R6JKWPH0VKJY5WCRKCZDZ3FPE1", "pox_address": "1PDKpmhYBVrv4XTBHZ2rGkuwweysNvom5R", "ustx": "3", "delegating": "0"} {"stx_address": "SP3STEY6JEYREF8Y023T82740V5B6DBE7QBCR4FMK", "pox_address": "16ecPjRJgaiEv81DcVewZm1qchkUrXtfk7", "ustx": "200", "delegating": "0"} {"stx_address": "SP2S1TVYFQV12RDNAT5JKC0NB0Y58TYCV2SRP1H4H", "pox_address": "1MD2VpS3kxDNTcPjARZPXPJTYeucVD3nm6", "ustx": "5", "delegating": "0"} {"stx_address": "SP35AF3G0M2GFA34S8R3VT0PRHQW563D4FH80JMCH", "pox_address": "12mb6aPVSJUNA6KxBLAKTKL8dXgn9y1XMZ", "ustx": "6", "delegating": "0"} {"stx_address": "SP3QXB26B01E5P6WZN7KAMFDHBCSPKTKR8X6RZ3M9", "pox_address": "1LhPVRPowv9fDzzpKg3ku4GQrAq5HpChFE", "ustx": "1000", "delegating": "0"} {"stx_address": "SP3C0VKAMZTBKYDEBG0BGSCS64EM425N2PN7QQZ8A", "pox_address": "1LhPVRPowv9fDzzpKg3ku4GQrAq5HpChFE", "ustx": "3000", "delegating": "0"} {"stx_address": "SP3RV0668H0F1WF9ZX290BEVK79AMK8KSPPQVNDPP", "pox_address": "1LhPVRPowv9fDzzpKg3ku4GQrAq5HpChFE", "ustx": "1", "delegating": "1"} EOF # extract stackers while read -r json; do local addr="$(echo "$json" | jq -r -c '.stx_address')" local locked="$(echo "$json" | jq -r -c '.ustx')" local until_ht=$(($cur_burn_ht + 2101)) local delegating="$(echo "$json" | jq -r -c '.delegating')" echo "{\"address\":\"$addr\",\"locked\":\"$locked\",\"until\":\"$until_ht\",\"type\":\"stacker\"}" >> "$test_dir/.stackers.txt" if [ "$delegating" = "1" ]; then echo "{\"address\":\"$addr\",\"until\":\"$until_ht\",\"type\":\"delegation\"}" >> "$test_dir/.stackers.txt" fi done < "$test_dir/stackers.json" result="$(main "$test_dir" "$tip" "$cur_burn_ht" < "$test_dir/.stackers.txt")" assert_eq "4004" "$(echo "$result" | jq -r -c '.yes')" assert_eq "100" "$(echo "$result" | jq -r -c '.no')" echo "ok" } run_local_tests() { deps_check test_btc_addr_to_stx_addr test_pubkey_to_btc_addr test_make_btc_addr test_stx_addr_to_clarity_principal test_decode_clarity_value test_is_stacker_delegating test_get_stacker_vote test_tabulate_vote test_main } run_bitcoin_tests() { deps_check test_bitcoin_ping test_get_scriptSig test_decode_scriptSig } run_stacks_tests() { deps_check test_get_stacker_info } run_tests() { run_local_tests run_bitcoin_tests run_stacks_tests } ############################################################### # # Entry point # ############################################################### set +u if [ -z "$TEST" ]; then TEST="" fi set -u if [ -n "$TEST" ]; then run_tests exit 0 fi main "$@"

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/exponentlabshq/stacks-clarity-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server