mirror of
https://github.com/aljazceru/cyphernode.git
synced 2025-12-17 20:55:04 +01:00
866 lines
33 KiB
Bash
866 lines
33 KiB
Bash
#!/bin/sh
|
|
|
|
. ./trace.sh
|
|
. ./sendtobitcoinnode.sh
|
|
|
|
createbatcher() {
|
|
trace "Entering createbatcher()..."
|
|
|
|
# POST http://192.168.111.152:8080/createbatcher
|
|
#
|
|
# args:
|
|
# - batcherLabel, optional, id can be used to reference the batcher
|
|
# - confTarget, optional, overriden by batchspend's confTarget, default Bitcoin Core conf_target will be used if not supplied
|
|
# NOTYET - feeRate, sat/vB, optional, overrides confTarget if supplied, overriden by batchspend's feeRate, default Bitcoin Core fee policy will be used if not supplied
|
|
#
|
|
# response:
|
|
# - batcherId, the batcher id
|
|
#
|
|
# BODY {"batcherLabel":"lowfees","confTarget":32}
|
|
# NOTYET BODY {"batcherLabel":"highfees","feeRate":231.8}
|
|
|
|
local request=${1}
|
|
local response
|
|
local label=$(echo "${request}" | jq ".batcherLabel")
|
|
trace "[createbatcher] label=${label}"
|
|
local conf_target=$(echo "${request}" | jq ".confTarget")
|
|
trace "[createbatcher] conf_target=${conf_target}"
|
|
local feerate=$(echo "${request}" | jq ".feeRate")
|
|
trace "[createbatcher] feerate=${feerate}"
|
|
|
|
# if [ "${feerate}" != "null" ]; then
|
|
# # If not null, let's nullify conf_target since feerate overrides it
|
|
# conf_target="null"
|
|
# trace "[createbatcher] Overriding conf_target=${conf_target}"
|
|
# fi
|
|
|
|
local batcher_id
|
|
|
|
batcher_id=$(sql "INSERT OR IGNORE INTO batcher (label, conf_target, feerate) VALUES (${label}, ${conf_target}, ${feerate}); SELECT LAST_INSERT_ROWID();")
|
|
|
|
if ("${batcher_id}" -eq "0"); then
|
|
trace "[createbatcher] Could not insert"
|
|
response='{"result":null,"error":{"code":-32700,"message":"Could not create batcher, label probably already exists","data":'${request}'}}'
|
|
else
|
|
trace "[createbatcher] Inserted"
|
|
response='{"result":{"batcherId":'${batcher_id}'},"error":null}'
|
|
fi
|
|
|
|
echo "${response}"
|
|
}
|
|
|
|
updatebatcher() {
|
|
trace "Entering updatebatcher()..."
|
|
|
|
# POST http://192.168.111.152:8080/updatebatcher
|
|
#
|
|
# args:
|
|
# - batcherId, optional, batcher id to update, will update default batcher if not supplied
|
|
# - batcherLabel, optional, id can be used to reference the batcher, will update default batcher if not supplied, if id is present then change the label with supplied text
|
|
# - confTarget, optional, new confirmation target for the batcher
|
|
# NOTYET - feeRate, sat/vB, optional, new feerate for the batcher
|
|
#
|
|
# response:
|
|
# - batcherId, the batcher id
|
|
# - batcherLabel, the batcher label
|
|
# - confTarget, the batcher default confirmation target
|
|
# NOTYET - feeRate, the batcher default feerate
|
|
#
|
|
# BODY {"batcherId":5,"confTarget":12}
|
|
# NOTYET BODY {"batcherLabel":"highfees","feeRate":400}
|
|
# NOTYET BODY {"batcherId":3,"batcherLabel":"ultrahighfees","feeRate":800}
|
|
# BODY {"batcherLabel":"fast","confTarget":2}
|
|
|
|
local request=${1}
|
|
local response
|
|
local whereclause
|
|
local returncode
|
|
|
|
local id=$(echo "${request}" | jq ".batcherId")
|
|
trace "[updatebatcher] id=${id}"
|
|
local label=$(echo "${request}" | jq ".batcherLabel")
|
|
trace "[updatebatcher] label=${label}"
|
|
local conf_target=$(echo "${request}" | jq ".confTarget")
|
|
trace "[updatebatcher] conf_target=${conf_target}"
|
|
local feerate=$(echo "${request}" | jq ".feeRate")
|
|
trace "[updatebatcher] feerate=${feerate}"
|
|
|
|
if [ "${id}" = "null" ] && [ "${label}" = "null" ]; then
|
|
# If id and label are null, use default batcher
|
|
trace "[updatebatcher] Using default batcher 1"
|
|
id=1
|
|
fi
|
|
|
|
# if [ "${feerate}" != "null" ]; then
|
|
# # If not null, let's nullify conf_target since feerate overrides it
|
|
# conf_target="null"
|
|
# trace "[updatebatcher] Overriding conf_target=${conf_target}"
|
|
# fi
|
|
|
|
if [ "${id}" = "null" ]; then
|
|
whereclause="label=${label}"
|
|
else
|
|
whereclause="id = ${id}"
|
|
fi
|
|
|
|
sql "UPDATE batcher set label=${label}, conf_target=${conf_target}, feerate=${feerate} WHERE ${whereclause}"
|
|
returncode=$?
|
|
trace_rc ${returncode}
|
|
if [ "${returncode}" -ne 0 ]; then
|
|
response='{"result":null,"error":{"code":-32700,"message":"Could not update batcher","data":'${request}'}}'
|
|
else
|
|
response='{"result":{"batcherId":'${id}'},"error":null}'
|
|
fi
|
|
|
|
echo "${response}"
|
|
}
|
|
|
|
addtobatch() {
|
|
trace "Entering addtobatch()..."
|
|
|
|
# POST http://192.168.111.152:8080/addtobatch
|
|
#
|
|
# args:
|
|
# - address, required, desination address
|
|
# - amount, required, amount to send to the destination address
|
|
# - outputLabel, optional, if you want to reference this output
|
|
# - batcherId, optional, the id of the batcher to which the output will be added, default batcher if not supplied, overrides batcherLabel
|
|
# - batcherLabel, optional, the label of the batcher to which the output will be added, default batcher if not supplied
|
|
# - webhookUrl, optional, the webhook to call when the batch is broadcast
|
|
#
|
|
# response:
|
|
# - batcherId, the id of the batcher
|
|
# - outputId, the id of the added output
|
|
# - nbOutputs, the number of outputs currently in the batch
|
|
# - oldest, the timestamp of the oldest output in the batch
|
|
# - total, the current sum of the batch's output amounts
|
|
#
|
|
# BODY {"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","amount":0.00233}
|
|
# BODY {"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","amount":0.00233,"batcherId":34,"webhookUrl":"https://myCypherApp:3000/batchExecuted"}
|
|
# BODY {"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","amount":0.00233,"batcherLabel":"lowfees","webhookUrl":"https://myCypherApp:3000/batchExecuted"}
|
|
# BODY {"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","amount":0.00233,"batcherId":34,"webhookUrl":"https://myCypherApp:3000/batchExecuted"}
|
|
|
|
local request=${1}
|
|
local response
|
|
local returncode=0
|
|
local inserted_id
|
|
local row
|
|
|
|
local address=$(echo "${request}" | jq -r ".address")
|
|
trace "[addtobatch] address=${address}"
|
|
local amount=$(echo "${request}" | jq ".amount")
|
|
trace "[addtobatch] amount=${amount}"
|
|
local label=$(echo "${request}" | jq ".outputLabel")
|
|
trace "[addtobatch] label=${label}"
|
|
local batcher_id=$(echo "${request}" | jq ".batcherId")
|
|
trace "[addtobatch] batcher_id=${batcher_id}"
|
|
local batcher_label=$(echo "${request}" | jq ".batcherLabel")
|
|
trace "[addtobatch] batcher_label=${batcher_label}"
|
|
local webhook_url=$(echo "${request}" | jq ".webhookUrl")
|
|
trace "[addtobatch] webhook_url=${webhook_url}"
|
|
|
|
local isvalid
|
|
isvalid=$(validateaddress "${address}" | jq ".result.isvalid")
|
|
if [ "${isvalid}" != "true" ]; then
|
|
|
|
response='{"result":null,"error":{"code":-32700,"message":"Invalid address","data":'${request}'}}'
|
|
|
|
trace "[addtobatch] Invalid address"
|
|
trace "[addtobatch] responding=${response}"
|
|
|
|
echo "${response}"
|
|
|
|
return 1
|
|
fi
|
|
|
|
if [ "${batcher_id}" = "null" ] && [ "${batcher_label}" = "null" ]; then
|
|
# If batcher_id and batcher_label are null, use default batcher
|
|
trace "[addtobatch] Using default batcher 1"
|
|
batcher_id=1
|
|
fi
|
|
|
|
if [ "${batcher_id}" = "null" ]; then
|
|
# Using batcher_label
|
|
batcher_id=$(sql "SELECT id FROM batcher WHERE label=${batcher_label}")
|
|
returncode=$?
|
|
trace_rc ${returncode}
|
|
fi
|
|
|
|
if [ -z "${batcher_id}" ]; then
|
|
# batcherLabel not found
|
|
response='{"result":null,"error":{"code":-32700,"message":"batcher not found","data":'${request}'}}'
|
|
else
|
|
inserted_id=$(sql "INSERT INTO recipient (address, amount, webhook_url, batcher_id, label) VALUES (\"${address}\", ${amount}, ${webhook_url}, ${batcher_id}, ${label}); SELECT LAST_INSERT_ROWID();")
|
|
returncode=$?
|
|
trace_rc ${returncode}
|
|
|
|
if [ "${returncode}" -ne 0 ]; then
|
|
response='{"result":null,"error":{"code":-32700,"message":"Could not add to batch","data":'${request}'}}'
|
|
else
|
|
row=$(sql "SELECT COUNT(id), MIN(inserted_ts), SUM(amount) FROM recipient WHERE tx_id IS NULL AND batcher_id=${batcher_id}")
|
|
returncode=$?
|
|
trace_rc ${returncode}
|
|
|
|
local count=$(echo "${row}" | cut -d '|' -f1)
|
|
trace "[addtobatch] count=${count}"
|
|
local oldest=$(echo "${row}" | cut -d '|' -f2)
|
|
trace "[addtobatch] oldest=${oldest}"
|
|
local total=$(echo "${row}" | cut -d '|' -f3)
|
|
trace "[addtobatch] total=${total}"
|
|
|
|
response='{"result":{"batcherId":'${batcher_id}',"outputId":'${inserted_id}',"nbOutputs":'${count}',"oldest":"'${oldest}'","total":'${total}'},"error":null}'
|
|
fi
|
|
fi
|
|
|
|
echo "${response}"
|
|
}
|
|
|
|
removefrombatch() {
|
|
trace "Entering removefrombatch()..."
|
|
|
|
# POST http://192.168.111.152:8080/removefrombatch
|
|
#
|
|
# args:
|
|
# - outputId, required, id of the output to remove
|
|
#
|
|
# response:
|
|
# - batcherId, the id of the batcher
|
|
# - outputId, the id of the removed output if found
|
|
# - nbOutputs, the number of outputs currently in the batch
|
|
# - oldest, the timestamp of the oldest output in the batch
|
|
# - total, the current sum of the batch's output amounts
|
|
#
|
|
# BODY {"id":72}
|
|
|
|
local request=${1}
|
|
local response
|
|
local returncode=0
|
|
local row
|
|
local batcher_id
|
|
|
|
local id=$(echo "${request}" | jq ".outputId")
|
|
trace "[removefrombatch] id=${id}"
|
|
|
|
if [ "${id}" = "null" ]; then
|
|
# id is required
|
|
trace "[removefrombatch] id missing"
|
|
response='{"result":null,"error":{"code":-32700,"message":"outputId is required","data":'${request}'}}'
|
|
else
|
|
batcher_id=$(sql "SELECT batcher_id FROM recipient WHERE id=${id}")
|
|
returncode=$?
|
|
trace_rc ${returncode}
|
|
|
|
if [ -n "${batcher_id}" ]; then
|
|
sql "DELETE FROM recipient WHERE id=${id}"
|
|
returncode=$?
|
|
trace_rc ${returncode}
|
|
|
|
if [ "${returncode}" -ne 0 ]; then
|
|
response='{"result":null,"error":{"code":-32700,"message":"Output was not removed","data":'${request}'}}'
|
|
else
|
|
row=$(sql "SELECT COUNT(id), COALESCE(MIN(inserted_ts), 0), COALESCE(SUM(amount), 0.00000000) FROM recipient WHERE tx_id IS NULL AND batcher_id=${batcher_id}")
|
|
returncode=$?
|
|
trace_rc ${returncode}
|
|
|
|
local count=$(echo "${row}" | cut -d '|' -f1)
|
|
trace "[removefrombatch] count=${count}"
|
|
local oldest=$(echo "${row}" | cut -d '|' -f2)
|
|
trace "[removefrombatch] oldest=${oldest}"
|
|
local total=$(echo "${row}" | cut -d '|' -f3)
|
|
trace "[removefrombatch] total=${total}"
|
|
|
|
response='{"result":{"batcherId":'${batcher_id}',"outputId":'${id}',"nbOutputs":'${count}',"oldest":"'${oldest}'","total":'${total}'},"error":null}'
|
|
fi
|
|
else
|
|
response='{"result":null,"error":{"code":-32700,"message":"Output not found or already spent","data":'${request}'}}'
|
|
fi
|
|
fi
|
|
|
|
echo "${response}"
|
|
}
|
|
|
|
batchspend() {
|
|
trace "Entering batchspend()..."
|
|
|
|
# POST http://192.168.111.152:8080/batchspend
|
|
#
|
|
# args:
|
|
# - batcherId, optional, id of the batcher to execute, overrides batcherLabel, default batcher will be spent if not supplied
|
|
# - batcherLabel, optional, label of the batcher to execute, default batcher will be executed if not supplied
|
|
# - confTarget, optional, overrides default value of createbatcher, default to value of createbatcher, default Bitcoin Core conf_target will be used if not supplied
|
|
# NOTYET - feeRate, optional, overrides confTarget if supplied, overrides default value of createbatcher, default to value of createbatcher, default Bitcoin Core value will be used if not supplied
|
|
#
|
|
# response:
|
|
# - batcherId, id of the executed batcher
|
|
# - confTarget, conf_target used for the spend
|
|
# - nbOutputs, the number of outputs spent in the batch
|
|
# - oldest, the timestamp of the oldest output in the spent batch
|
|
# - total, the sum of the spent batch's output amounts
|
|
# - txid, the batch transaction id
|
|
# - hash, the transaction hash
|
|
# - tx details: firstseen, size, vsize, replaceable, fee
|
|
# - outputs
|
|
#
|
|
# BODY {}
|
|
# BODY {"batcherId":"34","confTarget":12}
|
|
# NOTYET BODY {"batcherLabel":"highfees","feeRate":233.7}
|
|
# BODY {"batcherId":"411","confTarget":6}
|
|
|
|
local request=${1}
|
|
local response
|
|
local returncode=0
|
|
local row
|
|
local whereclause
|
|
|
|
local batcher_id=$(echo "${request}" | jq ".batcherId")
|
|
trace "[batchspend] batcher_id=${batcher_id}"
|
|
local batcher_label=$(echo "${request}" | jq ".batcherLabel")
|
|
trace "[batchspend] batcher_label=${batcher_label}"
|
|
local conf_target=$(echo "${request}" | jq ".confTarget")
|
|
trace "[batchspend] conf_target=${conf_target}"
|
|
local feerate=$(echo "${request}" | jq ".feeRate")
|
|
trace "[batchspend] feerate=${feerate}"
|
|
|
|
if [ "${batcher_id}" = "null" ] && [ "${batcher_label}" = "null" ]; then
|
|
# If batcher_id and batcher_label are null, use default batcher
|
|
trace "[batchspend] Using default batcher 1"
|
|
batcher_id=1
|
|
fi
|
|
|
|
if [ "${batcher_id}" = "null" ]; then
|
|
# Using batcher_label
|
|
whereclause="label=${batcher_label}"
|
|
else
|
|
whereclause="id=${batcher_id}"
|
|
fi
|
|
|
|
local batcher=$(sql "SELECT id, conf_target, feerate FROM batcher WHERE ${whereclause}")
|
|
returncode=$?
|
|
trace_rc ${returncode}
|
|
|
|
if [ -z "${batcher}" ]; then
|
|
# batcherLabel not found
|
|
response='{"result":null,"error":{"code":-32700,"message":"batcher not found","data":'${request}'}}'
|
|
else
|
|
# All good, let's try to batch spend!
|
|
|
|
# NOTYET
|
|
# We'll use supplied feerate
|
|
# If not supplied, we'll use supplied conf_target
|
|
# If not supplied, we'll use batcher default feerate
|
|
# If not set, we'll use batcher default conf_target
|
|
# If not set, default Bitcoin Core fee policy will be used
|
|
|
|
# We'll use the supplied conf_target
|
|
# If not supplied, we'll use the batcher default conf_target
|
|
# If not set, default Bitcoin Core fee policy will be used
|
|
|
|
# if [ "${feerate}" != "null" ]; then
|
|
# # If not null, let's nullify conf_target since feerate overrides it
|
|
# conf_target=
|
|
# trace "[batchspend] Overriding conf_target=${conf_target}"
|
|
# else
|
|
# if [ "${conf_target}" = "null" ]; then
|
|
# feerate=$(echo "${batcher}" | cut -d '|' -f3)
|
|
# if [ -z "${feerate}" ]; then
|
|
# # If null, let's use batcher conf_target
|
|
# conf_target=$(echo "${batcher}" | cut -d '|' -f2)
|
|
# fi
|
|
# fi
|
|
# fi
|
|
|
|
if [ "${conf_target}" = "null" ]; then
|
|
conf_target=$(echo "${batcher}" | cut -d '|' -f2)
|
|
trace "[batchspend] Using batcher default conf_target=${conf_target}"
|
|
fi
|
|
|
|
batcher_id=$(echo "${batcher}" | cut -d '|' -f1)
|
|
|
|
local batching=$(sql "SELECT address, amount, id, webhook_url FROM recipient WHERE tx_id IS NULL AND batcher_id=${batcher_id}")
|
|
trace "[batchspend] batching=${batching}"
|
|
|
|
local data
|
|
local recipientsjson
|
|
local webhooks_data
|
|
local id_inserted
|
|
local tx_details
|
|
local tx_raw_details
|
|
local address
|
|
local amount
|
|
local IFS=$'\n'
|
|
for row in ${batching}
|
|
do
|
|
trace "[batchspend] row=${row}"
|
|
address=$(echo "${row}" | cut -d '|' -f1)
|
|
trace "[batchspend] address=${address}"
|
|
amount=$(echo "${row}" | cut -d '|' -f2)
|
|
trace "[batchspend] amount=${amount}"
|
|
recipient_id=$(echo "${row}" | cut -d '|' -f3)
|
|
trace "[batchspend] recipient_id=${recipient_id}"
|
|
webhook_url=$(echo "${row}" | cut -d '|' -f4)
|
|
trace "[batchspend] webhook_url=${webhook_url}"
|
|
|
|
if [ -z "${recipientsjson}" ]; then
|
|
whereclause="\"${recipient_id}\""
|
|
recipientsjson="\"${address}\":${amount}"
|
|
webhooks_data="{\"outputId\":${recipient_id},\"address\":\"${address}\",\"amount\":${amount},\"webhookUrl\":\"${webhook_url}\"}"
|
|
else
|
|
whereclause="${whereclause},\"${recipient_id}\""
|
|
recipientsjson="${recipientsjson},\"${address}\":${amount}"
|
|
webhooks_data="${webhooks_data},{\"outputId\":${recipient_id},\"address\":\"${address}\",\"amount\":${amount},\"webhookUrl\":\"${webhook_url}\"}"
|
|
fi
|
|
done
|
|
|
|
local bitcoincore_args="{\"method\":\"sendmany\",\"params\":[\"\", {${recipientsjson}}"
|
|
if [ -n "${conf_target}" ]; then
|
|
bitcoincore_args="${bitcoincore_args}, 1, \"\", null, null, ${conf_target}"
|
|
fi
|
|
bitcoincore_args="${bitcoincore_args}]}"
|
|
data=$(send_to_spender_node "${bitcoincore_args}")
|
|
returncode=$?
|
|
trace_rc ${returncode}
|
|
trace "[batchspend] data=${data}"
|
|
|
|
if [ "${returncode}" -eq 0 ]; then
|
|
local txid=$(echo "${data}" | jq -r ".result")
|
|
trace "[batchspend] txid=${txid}"
|
|
|
|
# Let's get transaction details on the spending wallet so that we have fee information
|
|
tx_details=$(get_transaction ${txid} "spender")
|
|
tx_raw_details=$(get_rawtransaction ${txid})
|
|
|
|
# Amounts and fees are negative when spending so we absolute those fields
|
|
local tx_hash=$(echo "${tx_raw_details}" | jq '.result.hash')
|
|
local tx_ts_firstseen=$(echo "${tx_details}" | jq '.result.timereceived')
|
|
local tx_amount=$(echo "${tx_details}" | jq '.result.amount | fabs' | awk '{ printf "%.8f", $0 }')
|
|
local tx_size=$(echo "${tx_raw_details}" | jq '.result.size')
|
|
local tx_vsize=$(echo "${tx_raw_details}" | jq '.result.vsize')
|
|
local tx_replaceable=$(echo "${tx_details}" | jq -r '.result."bip125-replaceable"')
|
|
trace "[batchspend] tx_replaceable=${tx_replaceable}"
|
|
tx_replaceable=$([ "${tx_replaceable}" = "yes" ] && echo "true" || echo "false")
|
|
trace "[batchspend] tx_replaceable=${tx_replaceable}"
|
|
local fees=$(echo "${tx_details}" | jq '.result.fee | fabs' | awk '{ printf "%.8f", $0 }')
|
|
# Sometimes raw tx are too long to be passed as paramater, so let's write
|
|
# it to a temp file for it to be read by sqlite3 and then delete the file
|
|
echo "${tx_raw_details}" > batchspend-rawtx-${txid}.blob
|
|
|
|
# Get the info on the batch before setting it to done
|
|
row=$(sql "SELECT COUNT(id), COALESCE(MIN(inserted_ts), 0), COALESCE(SUM(amount), 0.00000000) FROM recipient WHERE tx_id IS NULL AND batcher_id=${batcher_id}")
|
|
|
|
# Let's insert the txid in our little DB -- then we'll already have it when receiving confirmation
|
|
id_inserted=$(sql "INSERT OR IGNORE INTO tx (txid, hash, confirmations, timereceived, fee, size, vsize, is_replaceable, conf_target, raw_tx) VALUES (\"${txid}\", ${tx_hash}, 0, ${tx_ts_firstseen}, ${fees}, ${tx_size}, ${tx_vsize}, ${tx_replaceable}, ${conf_target}, readfile('batchspend-rawtx-${txid}.blob')); SELECT LAST_INSERT_ROWID();")
|
|
returncode=$?
|
|
trace_rc ${returncode}
|
|
if [ "${returncode}" -eq 0 ]; then
|
|
if [ "${id_inserted}" -eq 0 ]; then
|
|
id_inserted=$(sql "SELECT id FROM tx WHERE txid=\"${txid}\"")
|
|
fi
|
|
trace "[batchspend] id_inserted: ${id_inserted}"
|
|
sql "UPDATE recipient SET tx_id=${id_inserted} WHERE id IN (${whereclause})"
|
|
trace_rc $?
|
|
fi
|
|
|
|
# Use the selected row above (before the insert)
|
|
local count=$(echo "${row}" | cut -d '|' -f1)
|
|
trace "[batchspend] count=${count}"
|
|
local oldest=$(echo "${row}" | cut -d '|' -f2)
|
|
trace "[batchspend] oldest=${oldest}"
|
|
local total=$(echo "${row}" | cut -d '|' -f3)
|
|
trace "[batchspend] total=${total}"
|
|
|
|
response='{"result":{"batcherId":'${batcher_id}',"confTarget":'${conf_target}',"nbOutputs":'${count}',"oldest":"'${oldest}'","total":'${total}
|
|
response="${response},\"status\":\"accepted\",\"txid\":\"${txid}\",\"hash\":${tx_hash},\"details\":{\"firstseen\":${tx_ts_firstseen},\"size\":${tx_size},\"vsize\":${tx_vsize},\"replaceable\":${tx_replaceable},\"fee\":${fees}},\"outputs\":[${webhooks_data}]}"
|
|
response="${response},\"error\":null}"
|
|
|
|
# Delete the temp file containing the raw tx (see above)
|
|
rm batchspend-rawtx-${txid}.blob
|
|
|
|
batch_webhooks "[${webhooks_data}]" '"batcherId":'${batcher_id}',"confTarget":'${conf_target}',"nbOutputs":'${count}',"oldest":"'${oldest}'","total":'${total}',"status":"accepted","txid":"'${txid}'","hash":'${tx_hash}',"details":{"firstseen":'${tx_ts_firstseen}',"size":'${tx_size}',"vsize":'${tx_vsize}',"replaceable":'${tx_replaceable}',"fee":'${fees}'}'
|
|
|
|
else
|
|
local message=$(echo "${data}" | jq -e ".error.message")
|
|
response='{"result":null,"error":{"code":-32700,"message":'${message}',"data":'${request}'}}'
|
|
fi
|
|
fi
|
|
|
|
trace "[batchspend] responding=${response}"
|
|
echo "${response}"
|
|
}
|
|
|
|
batch_check_webhooks() {
|
|
trace "Entering batch_check_webhooks()..."
|
|
|
|
local webhooks_data
|
|
local address
|
|
local amount
|
|
local recipient_id
|
|
local webhook_url
|
|
local batcher_id
|
|
local txid
|
|
local tx_hash
|
|
local tx_ts_firstseen
|
|
local tx_size
|
|
local tx_vsize
|
|
local tx_replaceable
|
|
local fees
|
|
local conf_target
|
|
local row
|
|
local count
|
|
local oldest
|
|
local total
|
|
local tx_id
|
|
|
|
local batching=$(sql "SELECT address, amount, r.id, webhook_url, b.id, t.txid, t.hash, t.timereceived, t.fee, t.size, t.vsize, t.is_replaceable, t.conf_target, t.id FROM recipient r, batcher b, tx t WHERE r.batcher_id=b.id AND r.tx_id=t.id AND NOT calledback AND tx_id IS NOT NULL AND webhook_url IS NOT NULL")
|
|
trace "[batch_check_webhooks] batching=${batching}"
|
|
|
|
local IFS=$'\n'
|
|
for row in ${batching}
|
|
do
|
|
trace "[batch_check_webhooks] row=${row}"
|
|
address=$(echo "${row}" | cut -d '|' -f1)
|
|
trace "[batch_check_webhooks] address=${address}"
|
|
amount=$(echo "${row}" | cut -d '|' -f2)
|
|
trace "[batch_check_webhooks] amount=${amount}"
|
|
recipient_id=$(echo "${row}" | cut -d '|' -f3)
|
|
trace "[batch_check_webhooks] recipient_id=${recipient_id}"
|
|
webhook_url=$(echo "${row}" | cut -d '|' -f4)
|
|
trace "[batch_check_webhooks] webhook_url=${webhook_url}"
|
|
batcher_id=$(echo "${row}" | cut -d '|' -f5)
|
|
trace "[batch_check_webhooks] batcher_id=${batcher_id}"
|
|
txid=$(echo "${row}" | cut -d '|' -f6)
|
|
trace "[batch_check_webhooks] txid=${txid}"
|
|
tx_hash=$(echo "${row}" | cut -d '|' -f7)
|
|
trace "[batch_check_webhooks] tx_hash=${tx_hash}"
|
|
tx_ts_firstseen=$(echo "${row}" | cut -d '|' -f8)
|
|
trace "[batch_check_webhooks] tx_ts_firstseen=${tx_ts_firstseen}"
|
|
fees=$(echo "${row}" | cut -d '|' -f9)
|
|
trace "[batch_check_webhooks] fees=${fees}"
|
|
tx_size=$(echo "${row}" | cut -d '|' -f10)
|
|
trace "[batch_check_webhooks] tx_size=${tx_size}"
|
|
tx_vsize=$(echo "${row}" | cut -d '|' -f11)
|
|
trace "[batch_check_webhooks] tx_vsize=${tx_vsize}"
|
|
tx_replaceable=$(echo "${row}" | cut -d '|' -f12)
|
|
trace "[batch_check_webhooks] tx_replaceable=${tx_replaceable}"
|
|
conf_target=$(echo "${row}" | cut -d '|' -f13)
|
|
trace "[batch_check_webhooks] conf_target=${conf_target}"
|
|
tx_id=$(echo "${row}" | cut -d '|' -f14)
|
|
trace "[batch_check_webhooks] tx_id=${tx_id}"
|
|
|
|
webhooks_data="{\"outputId\":${recipient_id},\"address\":\"${address}\",\"amount\":${amount},\"webhookUrl\":\"${webhook_url}\"}"
|
|
|
|
# I know this query for each output is not very efficient, but this function should not execute often, only in case of
|
|
# failed callbacks on batches...
|
|
# Get the info on the batch
|
|
row=$(sql "SELECT COUNT(id), COALESCE(MIN(inserted_ts), 0), COALESCE(SUM(amount), 0.00000000) FROM recipient r WHERE tx_id=\"${tx_id}\"")
|
|
|
|
# Use the selected row above
|
|
count=$(echo "${row}" | cut -d '|' -f1)
|
|
trace "[batchspend] count=${count}"
|
|
oldest=$(echo "${row}" | cut -d '|' -f2)
|
|
trace "[batchspend] oldest=${oldest}"
|
|
total=$(echo "${row}" | cut -d '|' -f3)
|
|
trace "[batchspend] total=${total}"
|
|
|
|
batch_webhooks "[${webhooks_data}]" '"batcherId":'${batcher_id}',"confTarget":'${conf_target}',"nbOutputs":'${count}',"oldest":"'${oldest}'","total":'${total}',"status":"accepted","txid":"'${txid}'","hash":"'${tx_hash}'","details":{"firstseen":'${tx_ts_firstseen}',"size":'${tx_size}',"vsize":'${tx_vsize}',"replaceable":'${tx_replaceable}',"fee":'${fees}'}'
|
|
done
|
|
}
|
|
|
|
batch_webhooks() {
|
|
trace "Entering batch_webhooks()..."
|
|
|
|
# webhooks_data:
|
|
# {"outputId":1,"address":"1abc","amount":0.12,"webhookUrl":"https://bleah.com/batchwebhook"}"
|
|
local webhooks_data=${1}
|
|
trace "[batch_webhooks] webhooks_data=${webhooks_data}"
|
|
|
|
# tx:
|
|
# {"batcherId":1,"txid":"abc123","hash":"abc123","details":{"firstseen":123123,"size":200,"vsize":141,"replaceable":true,"fee":0.00001}}'
|
|
local tx=${2}
|
|
trace "[batch_webhooks] tx=${tx}"
|
|
|
|
local outputs
|
|
local output_id
|
|
local address
|
|
local amount
|
|
local webhook_url
|
|
local body
|
|
local successful_recipient_ids
|
|
local returncode
|
|
|
|
outputs=$(echo "${webhooks_data}" | jq -Mc ".[]")
|
|
|
|
local output
|
|
local IFS=$'\n'
|
|
for output in ${outputs}
|
|
do
|
|
webhook_url=$(echo "${output}" | jq -r ".webhookUrl")
|
|
trace "[batch_webhooks] webhook_url=${webhook_url}"
|
|
|
|
if [ -z "${webhook_url}" ] || [ "${webhook_url}" = "null" ]; then
|
|
trace "[batch_webhooks] Empty webhook_url, skipping"
|
|
continue
|
|
fi
|
|
|
|
output_id=$(echo "${output}" | jq ".outputId")
|
|
trace "[batch_webhooks] output_id=${output_id}"
|
|
address=$(echo "${output}" | jq ".address")
|
|
trace "[batch_webhooks] address=${address}"
|
|
amount=$(echo "${output}" | jq ".amount")
|
|
trace "[batch_webhooks] amount=${amount}"
|
|
|
|
body='{"outputId":'${output_id}',"address":'${address}',"amount":'${amount}','${tx}'}'
|
|
trace "[batch_webhooks] body=${body}"
|
|
|
|
notify_web "${webhook_url}" "${body}" ${TOR_ADDR_WATCH_WEBHOOKS}
|
|
returncode=$?
|
|
trace_rc ${returncode}
|
|
|
|
if [ "${returncode}" -eq 0 ]; then
|
|
if [ -n "${successful_recipient_ids}" ]; then
|
|
successful_recipient_ids="${successful_recipient_ids},${output_id}"
|
|
else
|
|
successful_recipient_ids="${output_id}"
|
|
fi
|
|
else
|
|
trace "[batch_webhooks] callback failed, won't set to true in DB"
|
|
fi
|
|
done
|
|
|
|
sql "UPDATE recipient SET calledback=1, calledback_ts=CURRENT_TIMESTAMP WHERE id IN (${successful_recipient_ids})"
|
|
trace_rc $?
|
|
}
|
|
|
|
listbatchers() {
|
|
trace "Entering listbatchers()..."
|
|
|
|
# curl (GET) http://192.168.111.152:8080/listbatchers
|
|
#
|
|
# {"result":[
|
|
# {"batcherId":1,"batcherLabel":"default","confTarget":6,"nbOutputs":12,"oldest":123123,"total":0.86990143},
|
|
# {"batcherId":2,"batcherLabel":"lowfee","confTarget":32,"nbOutputs":44,"oldest":123123,"total":0.49827387},
|
|
# {"batcherId":3,"batcherLabel":"highfee","confTarget":2,"nbOutputs":7,"oldest":123123,"total":4.16843782}
|
|
# ],
|
|
# "error":null}
|
|
|
|
|
|
local batchers=$(sql "SELECT b.id, '{\"batcherId\":' || b.id || ',\"batcherLabel\":\"' || b.label || '\",\"confTarget\":' || conf_target || ',\"nbOutputs\":' || COUNT(r.id) || ',\"oldest\":\"' ||COALESCE(MIN(r.inserted_ts), 0) || '\",\"total\":' ||COALESCE(SUM(amount), 0.00000000) || '}' FROM batcher b LEFT JOIN recipient r ON r.batcher_id=b.id AND r.tx_id IS NULL GROUP BY b.id")
|
|
trace "[listbatchers] batchers=${batchers}"
|
|
|
|
local returncode
|
|
local response
|
|
local batcher
|
|
local jsonstring
|
|
local IFS=$'\n'
|
|
for batcher in ${batchers}
|
|
do
|
|
jsonstring=$(echo ${batcher} | cut -d '|' -f2)
|
|
if [ -z "${response}" ]; then
|
|
response='{"result":['${jsonstring}
|
|
else
|
|
response="${response},${jsonstring}"
|
|
fi
|
|
done
|
|
|
|
response=${response}'],"error":null}'
|
|
trace "[listbatchers] responding=${response}"
|
|
echo "${response}"
|
|
}
|
|
|
|
getbatcher() {
|
|
trace "Entering getbatcher()..."
|
|
|
|
# POST (GET) http://192.168.111.152:8080/getbatcher
|
|
#
|
|
# args:
|
|
# - batcherId, optional, id of the batcher, overrides batcerhLabel, default batcher will be used if not supplied
|
|
# - batcherLabel, optional, label of the batcher, default batcher will be used if not supplied
|
|
#
|
|
# response:
|
|
# {"result":{"batcherId":1,"batcherLabel":"default","confTarget":6,"nbOutputs":12,"oldest":123123,"total":0.86990143},"error":null}
|
|
#
|
|
# BODY {}
|
|
# BODY {"batcherId":34}
|
|
|
|
local request=${1}
|
|
local response
|
|
local returncode=0
|
|
local batcher
|
|
local whereclause
|
|
|
|
local batcher_id=$(echo "${request}" | jq ".batcherId")
|
|
trace "[getbatcher] batcher_id=${batcher_id}"
|
|
local batcher_label=$(echo "${request}" | jq ".batcherLabel")
|
|
trace "[getbatcher] batcher_label=${batcher_label}"
|
|
|
|
if [ "${batcher_id}" = "null" ] && [ "${batcher_label}" = "null" ]; then
|
|
# If batcher_id and batcher_label are null, use default batcher
|
|
trace "[getbatcher] Using default batcher 1"
|
|
batcher_id=1
|
|
fi
|
|
|
|
if [ "${batcher_id}" = "null" ]; then
|
|
# Using batcher_label
|
|
whereclause="b.label=${batcher_label}"
|
|
else
|
|
# Using batcher_id
|
|
whereclause="b.id=${batcher_id}"
|
|
fi
|
|
|
|
batcher=$(sql "SELECT b.id, '{\"batcherId\":' || b.id || ',\"batcherLabel\":\"' || b.label || '\",\"confTarget\":' || conf_target || ',\"nbOutputs\":' || COUNT(r.id) || ',\"oldest\":\"' ||COALESCE(MIN(r.inserted_ts), 0) || '\",\"total\":' ||COALESCE(SUM(amount), 0.00000000) || '}' FROM batcher b LEFT JOIN recipient r ON r.batcher_id=b.id AND r.tx_id IS NULL WHERE ${whereclause} GROUP BY b.id")
|
|
trace "[getbatcher] batcher=${batcher}"
|
|
|
|
if [ -n "${batcher}" ]; then
|
|
batcher=$(echo "${batcher}" | cut -d '|' -f2)
|
|
response='{"result":'${batcher}',"error":null}'
|
|
else
|
|
response='{"result":null,"error":{"code":-32700,"message":"batcher not found","data":'${request}'}}'
|
|
fi
|
|
|
|
echo "${response}"
|
|
}
|
|
|
|
getbatchdetails() {
|
|
trace "Entering getbatchdetails()..."
|
|
|
|
# POST (GET) http://192.168.111.152:8080/getbatchdetails
|
|
#
|
|
# args:
|
|
# - batcherId, optional, id of the batcher, overrides batcherLabel, default batcher will be used if not supplied
|
|
# - batcherLabel, optional, label of the batcher, default batcher will be used if not supplied
|
|
# - txid, optional, if you want the details of an executed batch, supply the batch txid, will return current pending batch
|
|
# if not supplied
|
|
#
|
|
# response:
|
|
# {"result":{
|
|
# "batcherId":34,
|
|
# "batcherLabel":"Special batcher for a special client",
|
|
# "confTarget":6,
|
|
# "nbOutputs":83,
|
|
# "oldest":123123,
|
|
# "total":10.86990143,
|
|
# "txid":"af867c86000da76df7ddb1054b273ca9e034e8c89d049b5b2795f9f590f67648",
|
|
# "hash":"af867c86000da76df7ddb1054b273ca9e034e8c89d049b5b2795f9f590f67648",
|
|
# "details":{
|
|
# "firstseen":123123,
|
|
# "size":424,
|
|
# "vsize":371,
|
|
# "replaceable":yes,
|
|
# "fee":0.00004112
|
|
# },
|
|
# "outputs":[
|
|
# "1abc":0.12,
|
|
# "3abc":0.66,
|
|
# "bc1abc":2.848,
|
|
# ...
|
|
# ]
|
|
# }
|
|
# },"error":null}
|
|
#
|
|
# BODY {}
|
|
# BODY {"batcherId":34}
|
|
|
|
local request=${1}
|
|
local response
|
|
local returncode=0
|
|
local batch
|
|
local tx
|
|
local outputsjson
|
|
local whereclause
|
|
|
|
local batcher_id=$(echo "${request}" | jq ".batcherId")
|
|
trace "[getbatchdetails] batcher_id=${batcher_id}"
|
|
local batcher_label=$(echo "${request}" | jq ".batcherLabel")
|
|
trace "[getbatchdetails] batcher_label=${batcher_label}"
|
|
local txid=$(echo "${request}" | jq ".txid")
|
|
trace "[getbatchdetails] txid=${txid}"
|
|
|
|
if [ "${batcher_id}" = "null" ] && [ "${batcher_label}" = "null" ]; then
|
|
# If batcher_id and batcher_label are null, use default batcher
|
|
trace "[getbatchdetails] Using default batcher 1"
|
|
batcher_id=1
|
|
fi
|
|
|
|
if [ "${batcher_id}" = "null" ]; then
|
|
# Using batcher_label
|
|
whereclause="b.label=${batcher_label}"
|
|
else
|
|
# Using batcher_id
|
|
whereclause="b.id=${batcher_id}"
|
|
fi
|
|
|
|
if [ "${txid}" != "null" ]; then
|
|
# Using txid
|
|
whereclause="${whereclause} AND t.txid=${txid}"
|
|
else
|
|
# null txid
|
|
whereclause="${whereclause} AND t.txid IS NULL"
|
|
outerclause="AND r.tx_id IS NULL"
|
|
fi
|
|
|
|
# First get the batch summary
|
|
batch=$(sql "SELECT b.id, COALESCE(t.id, NULL), '{\"batcherId\":' || b.id || ',\"batcherLabel\":\"' || b.label || '\",\"confTarget\":' || conf_target || ',\"nbOutputs\":' || COUNT(r.id) || ',\"oldest\":\"' ||COALESCE(MIN(r.inserted_ts), 0) || '\",\"total\":' ||COALESCE(SUM(amount), 0.00000000) FROM batcher b LEFT JOIN recipient r ON r.batcher_id=b.id ${outerclause} LEFT JOIN tx t ON t.id=r.tx_id WHERE ${whereclause} GROUP BY b.id")
|
|
trace "[getbatchdetails] batch=${batch}"
|
|
|
|
if [ -n "${batch}" ]; then
|
|
local tx_id
|
|
local outputs
|
|
|
|
tx_id=$(echo "${batch}" | cut -d '|' -f2)
|
|
trace "[getbatchdetails] tx_id=${tx_id}"
|
|
if [ -n "${tx_id}" ]; then
|
|
# Using txid
|
|
outerclause="AND r.tx_id=${tx_id}"
|
|
|
|
tx=$(sql "SELECT '\"txid\":\"' || txid || '\",\"hash\":\"' || hash || '\",\"details\":{\"firstseen\":' || timereceived || ',\"size\":' || size || ',\"vsize\":' || vsize || ',\"replaceable\":' || is_replaceable || ',\"fee\":' || fee || '}' FROM tx WHERE id=${tx_id}")
|
|
else
|
|
# null txid
|
|
outerclause="AND r.tx_id IS NULL"
|
|
fi
|
|
|
|
batcher_id=$(echo "${batch}" | cut -d '|' -f1)
|
|
outputs=$(sql "SELECT '{\"outputId\":' || id || ',\"outputLabel\":\"' || COALESCE(label, '') || '\",\"address\":\"' || address || '\",\"amount\":' || amount || ',\"addedTimestamp\":\"' || inserted_ts || '\"}' FROM recipient r WHERE batcher_id=${batcher_id} ${outerclause}")
|
|
|
|
local output
|
|
local IFS=$'\n'
|
|
for output in ${outputs}
|
|
do
|
|
if [ -n "${outputsjson}" ]; then
|
|
outputsjson="${outputsjson},${output}"
|
|
else
|
|
outputsjson="${output}"
|
|
fi
|
|
done
|
|
|
|
batch=$(echo "${batch}" | cut -d '|' -f3)
|
|
|
|
response='{"result":'${batch}
|
|
if [ -n "${tx}" ]; then
|
|
response=${response}','${tx}
|
|
else
|
|
response=${response}',"txid":null,"hash":null'
|
|
fi
|
|
response=${response}',"outputs":['${outputsjson}']},"error":null}'
|
|
else
|
|
response='{"result":null,"error":{"code":-32700,"message":"batch not found or no corresponding txid","data":'${request}'}}'
|
|
fi
|
|
|
|
echo "${response}"
|
|
|
|
}
|
|
|
|
# curl localhost:8888/listbatchers | jq
|
|
# curl -d '{}' localhost:8888/getbatcher | jq
|
|
# curl -d '{}' localhost:8888/getbatchdetails | jq
|
|
# curl -d '{"outputLabel":"test002","address":"1abd","amount":0.0002}' localhost:8888/addtobatch | jq
|
|
# curl -d '{}' localhost:8888/batchspend | jq
|
|
# curl -d '{"outputId":1}' localhost:8888/removefrombatch | jq
|
|
|
|
# curl -d '{"batcherLabel":"lowfees","confTarget":32}' localhost:8888/createbatcher | jq
|
|
# curl localhost:8888/listbatchers | jq
|
|
|
|
# curl -d '{"batcherLabel":"lowfees"}' localhost:8888/getbatcher | jq
|
|
# curl -d '{"batcherLabel":"lowfees"}' localhost:8888/getbatchdetails | jq
|
|
# curl -d '{"batcherLabel":"lowfees","outputLabel":"test002","address":"1abd","amount":0.0002}' localhost:8888/addtobatch | jq
|
|
# curl -d '{"batcherLabel":"lowfees"}' localhost:8888/batchspend | jq
|
|
# curl -d '{"batcherLabel":"lowfees","outputId":9}' localhost:8888/removefrombatch | jq
|