First pass on watchxpub

This commit is contained in:
kexkey
2019-02-08 16:24:53 -05:00
committed by kexkey
parent 5eb7d2d45c
commit fb49c3a060
20 changed files with 490 additions and 54 deletions

View File

@@ -3,6 +3,7 @@
# Watcher can:
action_watch=watcher
action_unwatch=watcher
action_watchxpub=watcher
action_getactivewatches=watcher
action_getbestblockhash=watcher
action_getbestblockinfo=watcher

View File

@@ -157,5 +157,5 @@ id="003";h64=$(echo -n "{\"alg\":\"HS256\",\"typ\":\"JWT\"}" | base64);p64=$(ech
echo "GET /getbestblockinfo" | docker run --rm -i --network=cyphernodenet alpine nc proxy:8888 -
echo "GET /getbalance" | docker run --rm -i --network=cyphernodenet alpine nc proxy:8888 -
echo "GET /ln_getinfo" | docker run --rm -i --network=cyphernodenet alpine nc proxy:8888 -
docker exec -it `docker ps -q -f name=cyphernodestack_cyphernode` curl -H "Content-Type: application/json" -d "{\"pub32\":\"upub5GtUcgGed1aGH4HKQ3vMYrsmLXwmHhS1AeX33ZvDgZiyvkGhNTvGd2TA5Lr4v239Fzjj4ZY48t6wTtXUy2yRgapf37QHgt6KWEZ6bgsCLpb\",\"path\":\"0/25-30\"}" proxy:8888/derivepubpath
docker exec -it `docker ps -q -f name=cyphernode_proxy` curl -H "Content-Type: application/json" -d "{\"pub32\":\"upub5GtUcgGed1aGH4HKQ3vMYrsmLXwmHhS1AeX33ZvDgZiyvkGhNTvGd2TA5Lr4v239Fzjj4ZY48t6wTtXUy2yRgapf37QHgt6KWEZ6bgsCLpb\",\"path\":\"0/25-30\"}" proxy:8888/derivepubpath
```

View File

@@ -40,6 +40,7 @@ services:
- "WATCHER_BTC_NODE_PRUNED=false"
- "OTSCLIENT_CONTAINER=otsclient:6666"
- "OTS_FILES=/proxy/otsfiles"
- "XPUB_DERIVATION_GAP=100"
image: cyphernode/proxy:latest
volumes:

View File

@@ -23,6 +23,7 @@ const defaultAPIProperties = `
# Watcher can:
action_watch=watcher
action_unwatch=watcher
action_watchxpub=watcher
action_getactivewatches=watcher
action_getbestblockhash=watcher
action_getbestblockinfo=watcher

View File

@@ -30,10 +30,12 @@ services:
# Bitcoin Mini Proxy
environment:
- "TRACING=1"
- "WATCHER_BTC_NODE_RPC_URL=<%= (bitcoin_mode === 'internal')?'bitcoin':bitcoin_node_ip %>:<%= (net === 'mainnet')?'8332':'18332' %>/wallet/watching01.dat"
- "WATCHER_BTC_NODE_RPC_URL=<%= (bitcoin_mode === 'internal')?'bitcoin':bitcoin_node_ip %>:<%= (net === 'mainnet')?'8332':'18332' %>/wallet"
- "WATCHER_BTC_NODE_DEFAULT_WALLET=watching01.dat"
- "WATCHER_BTC_NODE_RPC_USER=<%= bitcoin_rpcuser %>:<%= bitcoin_rpcpassword %>"
- "WATCHER_BTC_NODE_RPC_CFG=/tmp/watcher_btcnode_curlcfg.properties"
- "SPENDER_BTC_NODE_RPC_URL=<%= (bitcoin_mode === 'internal')?'bitcoin':bitcoin_node_ip %>:<%= (net === 'mainnet')?'8332':'18332' %>/wallet/spending01.dat"
- "SPENDER_BTC_NODE_RPC_URL=<%= (bitcoin_mode === 'internal')?'bitcoin':bitcoin_node_ip %>:<%= (net === 'mainnet')?'8332':'18332' %>/wallet"
- "SPENDER_BTC_NODE_DEFAULT_WALLET=spending01.dat"
- "SPENDER_BTC_NODE_RPC_USER=<%= bitcoin_rpcuser %>:<%= bitcoin_rpcpassword %>"
- "SPENDER_BTC_NODE_RPC_CFG=/tmp/spender_btcnode_curlcfg.properties"
- "PROXY_LISTENING_PORT=8888"
@@ -47,6 +49,7 @@ services:
- "WATCHER_BTC_NODE_PRUNED=<%= bitcoin_prune?'true':'false' %>"
- "OTSCLIENT_CONTAINER=otsclient:6666"
- "OTS_FILES=/proxy/otsfiles"
- "XPUB_DERIVATION_GAP=100"
image: cyphernode/proxy:<%= proxy_version %>
<% if ( devmode ) { %>
ports:

View File

@@ -42,6 +42,7 @@ OTS_CONTAINER=otsnode:6666
DERIVATION_PUB32=upub5GtUcgGed1aGH4HKQ3vMYrsmLXwmHhS1AeX33ZvDgZiyvkGhNTvGd2TA5Lr4v239Fzjj4ZY48t6wTtXUy2yRgapf37QHgt6KWEZ6bgsCLpb
DERIVATION_PATH=0/n
WATCHER_BTC_NODE_PRUNED=false
XPUB_DERIVATION_GAP=100
```
## Choose the right architecture

View File

@@ -1,5 +1,19 @@
PRAGMA foreign_keys = ON;
CREATE TABLE watching_by_pub32 (
id INTEGER PRIMARY KEY AUTOINCREMENT,
pub32 TEXT UNIQUE,
label TEXT UNIQUE,
derivation_path TEXT,
callback0conf TEXT,
callback1conf TEXT,
last_imported_n INTEGER,
watching INTEGER DEFAULT FALSE,
inserted_ts INTEGER DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_watching_by_pub32_pub32 ON watching_by_pub32 (pub32);
CREATE INDEX idx_watching_by_pub32_label ON watching_by_pub32 (label);
CREATE TABLE watching (
id INTEGER PRIMARY KEY AUTOINCREMENT,
address TEXT,
@@ -9,6 +23,8 @@ CREATE TABLE watching (
callback1conf TEXT,
calledback1conf INTEGER DEFAULT FALSE,
imported INTEGER DEFAULT FALSE,
watching_by_pub32_id INTEGER REFERENCES watching_by_pub32,
pub32_index INTEGER,
inserted_ts INTEGER DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_watching_address ON watching (address);

View File

@@ -1,10 +1,11 @@
#!/bin/sh
echo "Checking for OTS support in DB..."
sqlite3 db/proxydb ".tables" | grep "stamp" > /dev/null
if [ "$?" -eq "1" ]; then
# stamp not there, we have to migrate
echo "Migrating database from v0 to v0.1..."
echo "Migrating database for OTS support..."
cat sqlmigrate20181213_0-0.1.sql | sqlite3 $DB_FILE
else
echo "Database v0 to v0.1 migration already done, skipping!"
echo "Database OTS support migration already done, skipping!"
fi

View File

@@ -0,0 +1,11 @@
#!/bin/sh
echo "Checking for watch by xpub support in DB..."
sqlite3 db/proxydb ".tables" | grep "watching_by_pub32" > /dev/null
if [ "$?" -eq "1" ]; then
# watching_by_pub32 not there, we have to migrate
echo "Migrating database for watch by xpub support..."
cat sqlmigrate20190130_0.1-0.2.sql | sqlite3 $DB_FILE
else
echo "Database watch by xpub support migration already done, skipping!"
fi

View File

@@ -0,0 +1,18 @@
PRAGMA foreign_keys = ON;
CREATE TABLE watching_by_pub32 (
id INTEGER PRIMARY KEY AUTOINCREMENT,
pub32 TEXT UNIQUE,
label TEXT UNIQUE,
derivation_path TEXT,
callback0conf TEXT,
callback1conf TEXT,
last_imported_n INTEGER,
watching INTEGER DEFAULT FALSE,
inserted_ts INTEGER DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_watching_by_pub32_pub32 ON watching_by_pub32 (pub32);
CREATE INDEX idx_watching_by_pub32_label ON watching_by_pub32 (label);
ALTER TABLE watching ADD COLUMN watching_by_pub32_id INTEGER REFERENCES watching_by_pub32;
ALTER TABLE watching ADD COLUMN pub32_index INTEGER;

View File

@@ -21,6 +21,15 @@ deriveindex()
return $?
}
derivepubpath() {
trace "Entering derivepubpath()..."
# {"pub32":"tpubD6NzVbkrYhZ4YR3QK2tyfMMvBghAvqtNaNK1LTyDWcRHLcMUm3ZN2cGm5BS3MhCRCeCkXQkTXXjiJgqxpqXK7PeUSp86DTTgkLpcjMtpKWk","path":"0/25-30"}
send_to_pycoin $1
return $?
}
send_to_pycoin()
{
trace "Entering send_to_pycoin()..."

View File

@@ -11,7 +11,7 @@ do_callbacks()
trace "Entering do_callbacks()..."
# Let's fetch all the watching addresses still being watched but not called back
local callbacks=$(sql 'SELECT DISTINCT callback0conf, address, txid, vout, amount, confirmations, timereceived, fee, size, vsize, blockhash, blockheight, blocktime, w.id, is_replaceable FROM watching w LEFT JOIN watching_tx ON w.id = watching_id LEFT JOIN tx ON tx.id = tx_id WHERE NOT calledback0conf and watching_id NOT NULL and callback0conf NOT NULL and watching')
local callbacks=$(sql 'SELECT DISTINCT w.callback0conf, address, txid, vout, amount, confirmations, timereceived, fee, size, vsize, blockhash, blockheight, blocktime, w.id, is_replaceable, pub32_index, pub32, label, derivation_path FROM watching w LEFT JOIN watching_tx ON w.id = watching_id LEFT JOIN tx ON tx.id = tx_id LEFT JOIN watching_by_pub32 w32 ON watching_by_pub32_id = w32.id WHERE NOT calledback0conf AND watching_id NOT NULL AND w.callback0conf NOT NULL AND w.watching')
trace "[do_callbacks] callbacks0conf=${callbacks}"
local returncode
@@ -29,7 +29,7 @@ do_callbacks()
fi
done
callbacks=$(sql 'SELECT DISTINCT callback1conf, address, txid, vout, amount, confirmations, timereceived, fee, size, vsize, blockhash, blockheight, blocktime, w.id, is_replaceable FROM watching w, watching_tx wt, tx t WHERE w.id = watching_id AND tx_id = t.id AND NOT calledback1conf and confirmations>0 and callback1conf NOT NULL and watching')
callbacks=$(sql 'SELECT DISTINCT w.callback1conf, address, txid, vout, amount, confirmations, timereceived, fee, size, vsize, blockhash, blockheight, blocktime, w.id, is_replaceable, pub32_index, pub32, label, derivation_path FROM watching w, watching_tx wt, tx t LEFT JOIN watching_by_pub32 w32 ON watching_by_pub32_id = w32.id WHERE w.id = watching_id AND tx_id = t.id AND NOT calledback1conf AND confirmations>0 AND w.callback1conf NOT NULL AND w.watching')
trace "[do_callbacks] callbacks1conf=${callbacks}"
for row in ${callbacks}
@@ -66,6 +66,11 @@ build_callback()
local blocktime
local blockheight
local pub32_index
local pub32
local label
local derivation_path
# callback0conf, address, txid, vout, amount, confirmations, timereceived, fee, size, vsize, blockhash, blockheight, blocktime, w.id
trace "[build_callback] row=${row}"
@@ -79,7 +84,7 @@ build_callback()
trace "[build_callback] txid=${txid}"
vout_n=$(echo "${row}" | cut -d '|' -f4)
trace "[build_callback] vout_n=${vout_n}"
sent_amount=$(echo "${row}" | cut -d '|' -f5)
sent_amount=$(echo "${row}" | cut -d '|' -f5 | awk '{ printf "%.8f", $0 }')
trace "[build_callback] sent_amount=${sent_amount}"
confirmations=$(echo "${row}" | cut -d '|' -f6)
trace "[build_callback] confirmations=${confirmations}"
@@ -106,6 +111,17 @@ build_callback()
blocktime=$(echo "${row}" | cut -d '|' -f13)
trace "[build_callback] blocktime=${blocktime}"
pub32_index=$(echo "${row}" | cut -d '|' -f16)
trace "[build_callback] pub32_index=${pub32_index}"
if [ -n "${pub32_index}" ]; then
pub32=$(echo "${row}" | cut -d '|' -f17)
trace "[build_callback] pub32=${pub32}"
label=$(echo "${row}" | cut -d '|' -f18)
trace "[build_callback] label=${label}"
derivation_path=$(echo "${row}" | cut -d '|' -f19)
trace "[build_callback] derivation_path=${derivation_path}"
fi
data="{\"id\":\"${id}\","
data="${data}\"address\":\"${address}\","
data="${data}\"hash\":\"${txid}\","
@@ -124,6 +140,12 @@ build_callback()
data="${data}\"blocktime\":\"$(date -Is -d @${blocktime})\","
data="${data}\"blockheight\":${blockheight}"
fi
if [ -n "${pub32_index}" ]; then
data="${data}\"pub32\":\"${pub32}\","
data="${data}\"pub32_label\":\"${label}\","
derivation_path=$(echo -e $derivation_path | sed -En "s/n/${pub32_index}/p")
data="${data}\"pub32_derivation_path\":\"${derivation_path}\""
fi
data="${data}}"
trace "[build_callback] data=${data}"

View File

@@ -21,8 +21,10 @@ confirmation_request()
return $?
}
confirmation()
{
confirmation() {
(
flock -x 201
trace "Entering confirmation()..."
local returncode
@@ -56,7 +58,7 @@ confirmation()
notfirst=true
fi
done
local rows=$(sql "SELECT id, address FROM watching WHERE address IN (${addresseswhere}) AND watching")
local rows=$(sql "SELECT id, address, watching_by_pub32_id, pub32_index FROM watching WHERE address IN (${addresseswhere}) AND watching")
if [ ${#rows} -eq 0 ]; then
trace "[confirmation] No watched address in this tx!"
return 0
@@ -140,7 +142,7 @@ confirmation()
tx=$(sql "SELECT tx_id FROM watching_tx WHERE tx_id=\"${tx}\"")
if [ -z "${tx}" ]; then
trace "[confirmation] For this tx, there's no watching_tx row, let's create"
trace "[confirmation] For this tx, there's no watching_tx row, let's create it"
local watching_id
# If the tx is batched and pays multiple watched addresses, we have to insert
@@ -159,11 +161,27 @@ confirmation()
fi
########################################################################################################
########################################################################################################
# Let's now grow the watch window in the case of a xpub watcher...
for row in ${rows}
do
watching_by_pub32_id=$(echo "${row}" | cut -d '|' -f3)
pub32_index=$(echo "${row}" | cut -d '|' -f4)
if [ -n "${watching_by_pub32_id}" ]; then
extend_watchers ${watching_by_pub32_id} ${pub32_index}
fi
done
########################################################################################################
do_callbacks
echo '{"result":"confirmed"}'
return 0
) 201>./.confirmation.lock
}
case "${0}" in *confirmation.sh) confirmation $@;; esac

View File

@@ -3,19 +3,110 @@
. ./trace.sh
. ./sendtobitcoinnode.sh
importaddress_rpc()
{
trace "[Entering importaddress_rpc()]"
importaddress_rpc() {
trace "[Entering importaddress_rpc()]"
local address=${1}
local data="{\"method\":\"importaddress\",\"params\":[\"${address}\",\"\",false]}"
local result
result=$(send_to_watcher_node ${data})
local returncode=$?
local address=${1}
local data="{\"method\":\"importaddress\",\"params\":[\"${address}\",\"\",false]}"
local result
result=$(send_to_watcher_node ${data})
local returncode=$?
echo "${result}"
echo "${result}"
return ${returncode}
return ${returncode}
}
case "${0}" in *importaddress.sh) importaddress_rpc $@;; esac
importmulti_rpc() {
trace "[Entering importmulti_rpc()]"
local walletname=${1}
local addresses=$(echo "${2}" | jq ".addresses" | tr -d '\n ')
trace "[importmulti_rpc] addresses=${addresses}"
# [{"address":"2N6Q9kBcLtNswgMSLSQ5oduhbctk7hxEJW8"},{"address":"2NFLhFghAPKEPuZCKoeXYYxuaBxhKXbmhBV"},{"address":"2N7gepbQtRM5Hm4PTjvGadj9wAwEwnAsKiP"},{"address":"2Mth8XDZpXkY9d95tort8HYEAuEesow2tF6"},{"address":"2MwqEmAXhUw6H7bJwMhD13HGWVEj2HgFiNH"},{"address":"2N2Y4BVRdrRFhweub2ehHXveGZC3nryMEJw"}]
# [{"scriptPubKey":{"address":"2N6Q9kBcLtNswgMSLSQ5oduhbctk7hxEJW8"},"timestamp":"now","watchonly":true},{"scriptPubKey":{"address":"2NFLhFghAPKEPuZCKoeXYYxuaBxhKXbmhBV"},"timestamp":"now","watchonly":true},{"scriptPubKey":{"address":"2N7gepbQtRM5Hm4PTjvGadj9wAwEwnAsKiP"},"timestamp":"now","watchonly":true}]
# {"address":"2N6Q9kBcLtNswgMSLSQ5oduhbctk7hxEJW8"},
# {"scriptPubKey":{"address":"2N6Q9kBcLtNswgMSLSQ5oduhbctk7hxEJW8"},"timestamp":"now","watchonly":true},
addresses=$(echo "${addresses}" | sed "s/\"address\"/\"scriptPubKey\":\{\"address\"/g" | sed "s/}/},\"timestamp\":\"now\",\"watchonly\":true,\"label\":\"${walletname}\"}/g")
trace "[importmulti_rpc] addresses=${addresses}"
# {"method":"importmulti","params":["requests":[<req>],"options":{"rescan":false}]}
# <req> = {"address":"<addr>","timestamp":"now","watchonly":true},...
local rpcstring="{\"method\":\"importmulti\",\"params\":[${addresses},{\"rescan\":false}]}"
trace "[importmulti_rpc] rpcstring=${rpcstring}"
local result
# result=$(send_to_watcher_node_wallet ${walletname} ${rpcstring})
result=$(send_to_watcher_node ${rpcstring})
local returncode=$?
echo "${result}"
return ${returncode}
}
#[{"requests":
# [
# {"scriptPubKey":{"address":"2N6Q9kBcLtNswgMSLSQ5oduhbctk7hxEJW8"},"timestamp":"now","watchonly":true},
# {"scriptPubKey":{"address":"2NFLhFghAPKEPuZCKoeXYYxuaBxhKXbmhBV"},"timestamp":"now","watchonly":true},
# {"scriptPubKey":{"address":"2N7gepbQtRM5Hm4PTjvGadj9wAwEwnAsKiP"},"timestamp":"now","watchonly":true}
# ]},
#{"options":
# {
# "rescan":false
# }
#}]
#
# /usr/bin $ ./bitcoin-cli help importmulti
# importmulti "requests" ( "options" )
#
# Import addresses/scripts (with private or public keys, redeem script (P2SH)), rescanning all addresses in one-shot-only (rescan can be disabled via options). Requires a new wallet backup.
#
# Arguments:
# 1. requests (array, required) Data to be imported
# [ (array of json objects)
# {
# "scriptPubKey": "<script>" | { "address":"<address>" }, (string / json, required) Type of scriptPubKey (string for script, json for address)
# "timestamp": timestamp | "now" , (integer / string, required) Creation time of the key in seconds since epoch (Jan 1 1970 GMT),
# or the string "now" to substitute the current synced blockchain time. The timestamp of the oldest
# key will determine how far back blockchain rescans need to begin for missing wallet transactions.
# "now" can be specified to bypass scanning, for keys which are known to never have been used, and
# 0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest key
# creation time of all keys being imported by the importmulti call will be scanned.
# "redeemscript": "<script>" , (string, optional) Allowed only if the scriptPubKey is a P2SH address or a P2SH scriptPubKey
# "pubkeys": ["<pubKey>", ... ] , (array, optional) Array of strings giving pubkeys that must occur in the output or redeemscript
# "keys": ["<key>", ... ] , (array, optional) Array of strings giving private keys whose corresponding public keys must occur in the output or redeemscript
# "internal": <true> , (boolean, optional, default: false) Stating whether matching outputs should be treated as not incoming payments
# "watchonly": <true> , (boolean, optional, default: false) Stating whether matching outputs should be considered watched even when they're not spendable, only allowed if keys are empty
# "label": <label> , (string, optional, default: '') Label to assign to the address (aka account name, for now), only allowed with internal=false
# }
# ,...
# ]
# 2. options (json, optional)
# {
# "rescan": <false>, (boolean, optional, default: true) Stating if should rescan the blockchain after all imports
# }
#
# Note: This call can take over an hour to complete if rescan is true, during that time, other rpc calls
# may report that the imported keys, addresses or scripts exists but related transactions are still missing.
#
# Examples:
# > bitcoin-cli importmulti '[{ "scriptPubKey": { "address": "<my address>" }, "timestamp":1455191478 }, { "scriptPubKey": { "address": "<my 2nd address>" }, "label": "example 2", "timestamp": 1455191480 }]'
# > bitcoin-cli importmulti '[{ "scriptPubKey": { "address": "<my address>" }, "timestamp":1455191478 }]' '{ "rescan": false}'
#
# Response is an array with the same size as the input that has the execution result :
# [{ "success": true } , { "success": false, "error": { "code": -1, "message": "Internal Server Error"} }, ... ]
#

View File

@@ -86,6 +86,16 @@ main()
response_to_client "${response}" ${?}
break
;;
watchxpub)
# POST http://192.168.111.152:8080/watchxpub
# BODY {"label":"4421","pub32":"tpubD6NzVbkrYhZ4YR3QK2tyfMMvBghAvqtNaNK1LTyDWcRHLcMUm3ZN2cGm5BS3MhCRCeCkXQkTXXjiJgqxpqXK7PeUSp86DTTgkLpcjMtpKWk","path":"0/n","nstart":0,"unconfirmedCallbackURL":"192.168.111.233:1111/callback0conf","confirmedCallbackURL":"192.168.111.233:1111/callback1conf"}
# curl -H "Content-Type: application/json" -d "{\"label\":\"2219\",\"pub32\":\"upub5GtUcgGed1aGH4HKQ3vMYrsmLXwmHhS1AeX33ZvDgZiyvkGhNTvGd2TA5Lr4v239Fzjj4ZY48t6wTtXUy2yRgapf37QHgt6KWEZ6bgsCLpb\",\"path\":\"0/1/n\",\"nstart\":55,\"unconfirmedCallbackURL\":\"192.168.111.233:1111/callback0conf\",\"confirmedCallbackURL\":\"192.168.111.233:1111/callback1conf\"}" proxy:8888/watchxpub
response=$(watchpub32request "${line}")
response_to_client "${response}" ${?}
break
;;
getactivewatches)
# curl (GET) 192.168.111.152:8080/getactivewatches
@@ -188,7 +198,8 @@ main()
# BODY {"pub32":"upub5GtUcgGed1aGH4HKQ3vMYrsmLXwmHhS1AeX33ZvDgZiyvkGhNTvGd2TA5Lr4v239Fzjj4ZY48t6wTtXUy2yRgapf37QHgt6KWEZ6bgsCLpb","path":"0/25-30"}
# BODY {"pub32":"vpub5SLqN2bLY4WeZF3kL4VqiWF1itbf3A6oRrq9aPf16AZMVWYCuN9TxpAZwCzVgW94TNzZPNc9XAHD4As6pdnExBtCDGYRmNJrcJ4eV9hNqcv","path":"0/25-30"}
response=$(send_to_pycoin "${line}")
# response=$(send_to_pycoin "${line}")
response=$(derivepubpath "${line}")
response_to_client "${response}" ${?}
break
;;

View File

@@ -2,10 +2,20 @@
. ./trace.sh
send_to_watcher_node()
{
send_to_watcher_node() {
trace "Entering send_to_watcher_node()..."
send_to_bitcoin_node ${WATCHER_NODE_RPC_URL} ${WATCHER_NODE_RPC_CFG} $@
send_to_bitcoin_node ${WATCHER_NODE_RPC_URL}/${WATCHER_BTC_NODE_DEFAULT_WALLET} ${WATCHER_NODE_RPC_CFG} $@
local returncode=$?
trace_rc ${returncode}
return ${returncode}
}
send_to_watcher_node_wallet() {
trace "Entering send_to_watcher_node_wallet()..."
local walletname=$1
shift
trace "[send_to_watcher_node_wallet] walletname=${walletname}"
send_to_bitcoin_node ${WATCHER_NODE_RPC_URL}/$walletname ${WATCHER_NODE_RPC_CFG} $@
local returncode=$?
trace_rc ${returncode}
return ${returncode}
@@ -14,7 +24,7 @@ send_to_watcher_node()
send_to_spender_node()
{
trace "Entering send_to_spender_node()..."
send_to_bitcoin_node ${SPENDER_NODE_RPC_URL} ${SPENDER_NODE_RPC_CFG} $@
send_to_bitcoin_node ${SPENDER_NODE_RPC_URL}/${SPENDER_BTC_NODE_DEFAULT_WALLET} ${SPENDER_NODE_RPC_CFG} $@
local returncode=$?
trace_rc ${returncode}
return ${returncode}
@@ -30,7 +40,7 @@ send_to_bitcoin_node()
local config=${2}
local data=${3}
trace "[send_to_bitcoin_node] curl -s --user ${user} -H \"Content-Type: application/json\" -d \"${data}\" ${node_url}"
trace "[send_to_bitcoin_node] curl -s --config ${config} -H \"Content-Type: application/json\" -d \"${data}\" ${node_url}"
result=$(curl -s --config ${config} -H "Content-Type: application/json" -d "${data}" ${node_url})
returncode=$?
trace_rc ${returncode}

View File

@@ -2,11 +2,10 @@
. ./trace.sh
sql()
{
trace "sqlite3 ${DB_FILE} '${1}'"
sqlite3 -cmd ".timeout 20000" ${DB_FILE} "${1}"
sql() {
trace "sqlite3 -cmd \".timeout 20000\" ${DB_FILE} \"${1}\""
sqlite3 -cmd ".timeout 20000" ${DB_FILE} "${1}"
# sqlite3 ${DB_FILE} "PRAGMA busy_timeout=20000; ${1}"
return $?
}
case "${0}" in *sql.sh) sql $@;; esac

View File

@@ -3,8 +3,7 @@
. ./trace.sh
. ./sendtobitcoinnode.sh
spend()
{
spend() {
trace "Entering spend()..."
local data
@@ -26,7 +25,6 @@ spend()
trace "[spend] txid=${txid}"
# Let's insert the txid in our little DB to manage the confirmation and tell it's not a watching address
# sql "INSERT OR IGNORE INTO watching (watching, txid) VALUES (0, ${txid})"
sql "INSERT OR IGNORE INTO tx (txid) VALUES (\"${txid}\")"
trace_rc $?
id_inserted=$(sql "SELECT id FROM tx WHERE txid=\"${txid}\"")
@@ -47,8 +45,7 @@ spend()
return ${returncode}
}
getbalance()
{
getbalance() {
trace "Entering getbalance()..."
local response
@@ -74,8 +71,7 @@ getbalance()
return ${returncode}
}
getnewaddress()
{
getnewaddress() {
trace "Entering getnewaddress()..."
local response
@@ -101,8 +97,7 @@ getnewaddress()
return ${returncode}
}
addtobatching()
{
addtobatching() {
trace "Entering addtobatching()..."
local address=${1}
@@ -117,8 +112,7 @@ addtobatching()
return ${returncode}
}
batchspend()
{
batchspend() {
trace "Entering batchspend()..."
local data
@@ -165,8 +159,6 @@ batchspend()
trace "[batchspend] txid=${txid}"
# Let's insert the txid in our little DB to manage the confirmation and tell it's not a watching address
# sql "INSERT OR IGNORE INTO watching (watching, txid) VALUES (0, ${txid})"
# trace_rc $?
sql "INSERT OR IGNORE INTO tx (txid) VALUES (\"${txid}\")"
returncode=$?
trace_rc ${returncode}
@@ -190,4 +182,20 @@ batchspend()
return ${returncode}
}
#case "${0}" in *walletoperations.sh) getbalance $@;; esac
create_wallet() {
trace "[Entering create_wallet()]"
local walletname=${1}
local rpcstring="{\"method\":\"createwallet\",\"params\":[\"${walletname}\",true]}"
trace "[create_wallet] rpcstring=${rpcstring}"
local result
result=$(send_to_watcher_node ${rpcstring})
local returncode=$?
echo "${result}"
return ${returncode}
}

View File

@@ -4,9 +4,9 @@
. ./importaddress.sh
. ./sql.sh
. ./sendtobitcoinnode.sh
. ./bitcoin.sh
watchrequest()
{
watchrequest() {
trace "Entering watchrequest()..."
local returncode
@@ -71,4 +71,216 @@ watchrequest()
return ${returncode}
}
case "${0}" in *watchrequest.sh) watchrequest $@;; esac
watchpub32request() {
trace "Entering watchpub32request()..."
local returncode
local request=${1}
local label=$(echo "${request}" | jq ".label" | tr -d '"')
trace "[watchpub32request] label=${label}"
local pub32=$(echo "${request}" | jq ".pub32" | tr -d '"')
trace "[watchpub32request] pub32=${pub32}"
local path=$(echo "${request}" | jq ".path" | tr -d '"')
trace "[watchpub32request] path=${path}"
local nstart=$(echo "${request}" | jq ".nstart")
trace "[watchpub32request] nstart=${nstart}"
local cb0conf_url=$(echo "${request}" | jq ".unconfirmedCallbackURL" | tr -d '"')
trace "[watchpub32request] cb0conf_url=${cb0conf_url}"
local cb1conf_url=$(echo "${request}" | jq ".confirmedCallbackURL" | tr -d '"')
trace "[watchpub32request] cb1conf_url=${cb1conf_url}"
watchpub32 ${label} ${pub32} ${path} ${nstart} ${cb0conf_url} ${cb1conf_url}
returncode=$?
trace_rc ${returncode}
return ${returncode}
}
watchpub32() {
trace "Entering watchpub32()..."
local returncode
local label=${1}
trace "[watchpub32] label=${label}"
local pub32=${2}
trace "[watchpub32] pub32=${pub32}"
local path=${3}
trace "[watchpub32] path=${path}"
local nstart=${4}
trace "[watchpub32] nstart=${nstart}"
local last_n=$((${nstart}+${XPUB_DERIVATION_GAP}))
trace "[watchpub32] last_n=${last_n}"
local cb0conf_url=${5}
trace "[watchpub32] cb0conf_url=${cb0conf_url}"
local cb1conf_url=${6}
trace "[watchpub32] cb1conf_url=${cb1conf_url}"
local upto_n=${7}
trace "[watchpub32] upto_n=${upto_n}"
local id_inserted
local result
local error_msg
local data
# Derive with pycoin...
# {"pub32":"tpubD6NzVbkrYhZ4YR3QK2tyfMMvBghAvqtNaNK1LTyDWcRHLcMUm3ZN2cGm5BS3MhCRCeCkXQkTXXjiJgqxpqXK7PeUSp86DTTgkLpcjMtpKWk","path":"0/25-30"}
if [ -n "${upto_n}" ]; then
# If upto_n provided, then we create from nstart to upto_n (instead of + GAP)
last_n=${upto_n}
fi
local subspath=$(echo -e $path | sed -En "s/n/${nstart}-${last_n}/p")
trace "[watchpub32] subspath=${subspath}"
local addresses
addresses=$(derivepubpath "{\"pub32\":\"${pub32}\",\"path\":\"${subspath}\"}")
returncode=$?
trace_rc ${returncode}
trace "[watchpub32] addresses=${addresses}"
if [ "${returncode}" -eq 0 ]; then
# result=$(create_wallet "${pub32}")
# returncode=$?
# trace_rc ${returncode}
# trace "[watchpub32request] result=${result}"
trace "[watchpub32] Skipping create_wallet"
if [ "${returncode}" -eq 0 ]; then
# Importmulti in Bitcoin Core...
result=$(importmulti_rpc "${pub32}" "${addresses}")
returncode=$?
trace_rc ${returncode}
trace "[watchpub32] result=${result}"
if [ "${returncode}" -eq 0 ]; then
if [ -n "${upto_n}" ]; then
# Update existing row
sql "UPDATE watching_by_pub32 set last_imported_n=${upto_n} WHERE pub32=\"${pub32}\""
else
# Insert in our DB...
sql "INSERT OR IGNORE INTO watching_by_pub32 (pub32, label, derivation_path, watching, callback0conf, callback1conf, last_imported_n) VALUES (\"${pub32}\", \"${label}\", \"${path}\", 1, \"${cb0conf_url}\", \"${cb1conf_url}\", ${last_n})"
fi
returncode=$?
trace_rc ${returncode}
if [ "${returncode}" -eq 0 ]; then
id_inserted=$(sql "SELECT id FROM watching_by_pub32 WHERE label='${label}'")
trace "[watchpub32] id_inserted: ${id_inserted}"
addresses=$(echo ${addresses} | jq ".addresses[].address")
insert_watches "${addresses}" "${cb0conf_url}" "${cb1conf_url}" ${id_inserted} ${nstart}
else
error_msg="Can't insert xpub watcher in DB"
fi
else
error_msg="Can't import addresses"
fi
else
error_msg="Can't create wallet"
fi
else
error_msg="Can't derive addresses"
fi
if [ -z "${error_msg}" ]; then
data="{\"id\":\"${id_inserted}\",
\"event\":\"watchxpub\",
\"pub32\":\"${pub32}\",
\"label\":\"${label}\",
\"path\":\"${path}\",
\"nstart\":\"${nstart}\",
\"unconfirmedCallbackURL\":\"${cb0conf_url}\",
\"confirmedCallbackURL\":\"${cb1conf_url}\"}"
returncode=0
else
data="{\"error\":\"${error_msg}\",
\"event\":\"watchxpub\",
\"pub32\":\"${pub32}\",
\"label\":\"${label}\",
\"path\":\"${path}\",
\"nstart\":\"${nstart}\",
\"unconfirmedCallbackURL\":\"${cb0conf_url}\",
\"confirmedCallbackURL\":\"${cb1conf_url}\"}"
returncode=1
fi
trace "[watchpub32] responding=${data}"
echo "${data}"
return ${returncode}
}
insert_watches() {
trace "Entering insert_watches()..."
local addresses=${1}
local callback0conf=${2}
local callback1conf=${3}
local xpub_id=${4}
local nstart=${5}
local inserted_values=""
local IFS=$'\n'
for address in ${addresses}
do
# (address, watching, callback0conf, callback1conf, imported, watching_by_pub32_id)
if [ -n "${inserted_values}" ]; then
inserted_values="${inserted_values},"
fi
inserted_values="${inserted_values}(${address}, 1, \"${callback0conf}\", \"${callback1conf}\", 1"
if [ -n "${xpub_id}" ]; then
inserted_values="${inserted_values}, ${xpub_id}, ${nstart}"
nstart=$((${nstart} + 1))
fi
inserted_values="${inserted_values})"
done
trace "[insert_watches] inserted_values=${inserted_values}"
sql "INSERT OR IGNORE INTO watching (address, watching, callback0conf, callback1conf, imported, watching_by_pub32_id, pub32_index) VALUES ${inserted_values}"
returncode=$?
trace_rc ${returncode}
return ${returncode}
}
extend_watchers() {
trace "Entering extend_watchers()..."
local watching_by_pub32_id=${1}
trace "[extend_watchers] watching_by_pub32_id=${watching_by_pub32_id}"
local pub32_index=${2}
trace "[extend_watchers] pub32_index=${pub32_index}"
local upgrade_to_n=$((${pub32_index} + ${XPUB_DERIVATION_GAP}))
trace "[extend_watchers] upgrade_to_n=${upgrade_to_n}"
local last_imported_n
local row
row=$(sql "SELECT pub32, label, derivation_path, callback0conf, callback1conf, last_imported_n FROM watching_by_pub32 WHERE id=${watching_by_pub32_id}")
returncode=$?
trace_rc ${returncode}
trace "[extend_watchers] row=${row}"
local pub32=$(echo "${row}" | cut -d '|' -f1)
trace "[extend_watchers] pub32=${pub32}"
local label=$(echo "${row}" | cut -d '|' -f2)
trace "[extend_watchers] label=${label}"
local derivation_path=$(echo "${row}" | cut -d '|' -f3)
trace "[extend_watchers] derivation_path=${derivation_path}"
local callback0conf=$(echo "${row}" | cut -d '|' -f4)
trace "[extend_watchers] callback0conf=${callback0conf}"
local callback1conf=$(echo "${row}" | cut -d '|' -f5)
trace "[extend_watchers] callback1conf=${callback1conf}"
local last_imported_n=$(echo "${row}" | cut -d '|' -f6)
trace "[extend_watchers] last_imported_n=${last_imported_n}"
if [ "${last_imported_n}" -lt "${upgrade_to_n}" ]; then
# We want to keep our gap between last tx and last n watched...
# For example, if the last imported n is 155 and we just got a tx with pub32 index of 66,
# we want to extend the watched addresses to 166 if our gap is 100 (default).
watchpub32 ${label} ${pub32} ${derivation_path} $((${last_imported_n} + 1)) ${callback0conf} ${callback1conf} ${upgrade_to_n} > /dev/null
returncode=$?
trace_rc ${returncode}
fi
return ${returncode}
}

View File

@@ -1,8 +1,10 @@
TRACING=1
WATCHER_BTC_NODE_RPC_URL=bitcoin:18332/wallet/watching01.dat
WATCHER_BTC_NODE_RPC_URL=bitcoin:18332/wallet
WATCHER_BTC_NODE_DEFAULT_WALLET=watching01.dat
WATCHER_BTC_NODE_RPC_USER=rpc_username:rpc_password
WATCHER_BTC_NODE_RPC_CFG=/proxy/watcher_btcnode_curlcfg.properties
SPENDER_BTC_NODE_RPC_URL=bitcoin:18332/wallet/spending01.dat
SPENDER_BTC_NODE_RPC_URL=bitcoin:18332/wallet
SPENDER_BTC_NODE_DEFAULT_WALLET=spending01.dat
SPENDER_BTC_NODE_RPC_USER=rpc_username:rpc_password
SPENDER_BTC_NODE_RPC_CFG=/proxy/spender_btcnode_curlcfg.properties
PROXY_LISTENING_PORT=8888
@@ -18,3 +20,4 @@ OTS_FILES=/otsfiles
DERIVATION_PUB32=upub5GtUcgGed1aGH4HKQ3vMYrsmLXwmHhS1AeX33ZvDgZiyvkGhNTvGd2TA5Lr4v239Fzjj4ZY48t6wTtXUy2yRgapf37QHgt6KWEZ6bgsCLpb
DERIVATION_PATH=0/n
WATCHER_BTC_NODE_PRUNED=false
XPUB_DERIVATION_GAP=100