From 2e60b91a6ab1c54cf167ee1e313c035817e6e7d8 Mon Sep 17 00:00:00 2001 From: kexkey Date: Sun, 8 Aug 2021 14:47:28 -0400 Subject: [PATCH] Support for Bitcoin Core labels for watched and new addresses --- doc/API.v0.md | 21 +++++- doc/openapi/v0/cyphernode-api.yaml | 68 ++++++++++++++++++- .../data/sqlmigrate20210808_0.7.0-0.8.0.sh | 14 ++++ .../data/sqlmigrate20210808_0.7.0-0.8.0.sql | 9 +++ proxy_docker/app/script/getactivewatches.sh | 2 +- proxy_docker/app/script/importaddress.sh | 7 +- proxy_docker/app/script/manage_missed_conf.sh | 8 ++- proxy_docker/app/script/requesthandler.sh | 18 ++++- proxy_docker/app/script/walletoperations.sh | 32 +++++++-- proxy_docker/app/script/watchrequest.sh | 9 ++- 10 files changed, 170 insertions(+), 18 deletions(-) create mode 100644 proxy_docker/app/data/sqlmigrate20210808_0.7.0-0.8.0.sh create mode 100644 proxy_docker/app/data/sqlmigrate20210808_0.7.0-0.8.0.sql diff --git a/doc/API.v0.md b/doc/API.v0.md index c9e10c5..85b6182 100644 --- a/doc/API.v0.md +++ b/doc/API.v0.md @@ -9,7 +9,7 @@ Inserts the address, webhook URLs and eventMessage in the DB and imports the add ```http POST http://cyphernode:8888/watch with body... -{"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","unconfirmedCallbackURL":"192.168.111.233:1111/callback0conf","confirmedCallbackURL":"192.168.111.233:1111/callback1conf","eventMessage":"eyJib3VuY2VfYWRkcmVzcyI6IjJNdkEzeHIzOHIxNXRRZWhGblBKMVhBdXJDUFR2ZTZOamNGIiwibmJfY29uZiI6MH0K"} +{"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","unconfirmedCallbackURL":"192.168.111.233:1111/callback0conf","confirmedCallbackURL":"192.168.111.233:1111/callback1conf","eventMessage":"eyJib3VuY2VfYWRkcmVzcyI6IjJNdkEzeHIzOHIxNXRRZWhGblBKMVhBdXJDUFR2ZTZOamNGIiwibmJfY29uZiI6MH0K","label":"myLabel"} ``` Proxy response: @@ -23,6 +23,7 @@ Proxy response: "address": "2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp", "unconfirmedCallbackURL": "192.168.133.233:1111/callback0conf", "confirmedCallbackURL": "192.168.133.233:1111/callback1conf", + "label": "myLabel", "estimatesmartfee2blocks": "0.000010", "estimatesmartfee6blocks": "0.000010", "estimatesmartfee36blocks": "0.000010", @@ -721,6 +722,20 @@ GET http://cyphernode:8888/getnewaddress/legacy GET http://cyphernode:8888/getnewaddress/p2sh-segwit ``` +or + +```http +POST http://cyphernode:8888/getnewaddress +with body... +{"address_type":"bech32","label":"myLabel"} +or +{"label":"myLabel"} +or +{"address_type":"p2sh-segwit"} +or +{} +``` + Proxy response: ```json @@ -731,7 +746,9 @@ Proxy response: ```json { - "address":"tb1ql7yvh3lmajxmaljsnsu3w8lhwczu963tvjfzpj" + "address":"tb1ql7yvh3lmajxmaljsnsu3w8lhwczu963tvjfzpj", + "label":"myLabel", + "address_type":"bech32" } ``` diff --git a/doc/openapi/v0/cyphernode-api.yaml b/doc/openapi/v0/cyphernode-api.yaml index 68db867..e28f065 100644 --- a/doc/openapi/v0/cyphernode-api.yaml +++ b/doc/openapi/v0/cyphernode-api.yaml @@ -62,6 +62,9 @@ paths: eventMessage: description: "Will be part of the published message on confirmations" type: "string" + label: + description: "Label for this address that will be imported in Bitcoin Core" + type: "string" responses: '200': description: "successfully created" @@ -1096,8 +1099,16 @@ paths: application/json: schema: $ref: '#/components/schemas/ApiResponseTemporarilyUnavailable' - /getnewaddress: + /getnewaddress/{address_type}: get: + parameters: + - in: "path" + name: "address_type" + description: "Address type" + required: false + schema: + type: "string" + enum: ["legacy", "p2sh-segwit", "bech32"] tags: - "spending wallet" - "core features" @@ -1114,6 +1125,57 @@ paths: properties: address: $ref: '#/components/schemas/TypeAddressString' + address_type: + type: "string" + enum: ["legacy", "p2sh-segwit", "bech32"] + '403': + $ref: '#/components/schemas/ApiResponseNotAllowed' + '503': + description: "Resource temporarily unavailable" + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseTemporarilyUnavailable' + /getnewaddress: + post: + tags: + - "spending wallet" + - "core features" + summary: "Generates a new address on the spending wallet" + description: "Generates a new address on the spending wallet. Useful to refill the spending wallet from cold wallet (ie Trezor)." + operationId: "getSpendingWalletNewAddress" + requestBody: + description: "Bitcoin address properties" + required: false + content: + application/json: + schema: + type: "object" + properties: + address_type: + type: "string" + enum: ["legacy", "p2sh-segwit", "bech32"] + label: + type: "string" + responses: + '200': + description: "successfully got an address" + content: + application/json: + schema: + type: "object" + required: + - "address" + properties: + address: + $ref: '#/components/schemas/TypeAddressString' + address_type: + type: "string" + enum: ["legacy", "p2sh-segwit", "bech32"] + label: + type: "string" + '400': + $ref: '#/components/schemas/ApiResponseInvalidInput' '403': $ref: '#/components/schemas/ApiResponseNotAllowed' '503': @@ -2491,6 +2553,7 @@ components: - "unconfirmedCallbackURL" - "confirmedCallbackURL" - "eventMessage" + - "label" properties: id: type: "string" @@ -2512,6 +2575,9 @@ components: description: "Async callback in case of activity on address" type: "string" format: "url" + label: + description: "Label for this address that will be imported in Bitcoin Core" + type: "string" estimatesmartfee2blocks: type: "string" estimatesmartfee6blocks: diff --git a/proxy_docker/app/data/sqlmigrate20210808_0.7.0-0.8.0.sh b/proxy_docker/app/data/sqlmigrate20210808_0.7.0-0.8.0.sh new file mode 100644 index 0000000..f868254 --- /dev/null +++ b/proxy_docker/app/data/sqlmigrate20210808_0.7.0-0.8.0.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +echo "Checking for labels for watched addresses support in DB..." +count=$(sqlite3 $DB_FILE "select count(*) from pragma_table_info('watching') where name='label'") +if [ "${count}" -eq "0" ]; then + # label not there, we have to migrate + echo "Migrating database for labels for watched addresses support..." + echo "Backing up current DB..." + cp $DB_FILE $DB_FILE-sqlmigrate20210808_0.7.0-0.8.0 + echo "Altering DB..." + cat sqlmigrate20210808_0.7.0-0.8.0.sql | sqlite3 $DB_FILE +else + echo "Database labels for watched addresses support migration already done, skipping!" +fi diff --git a/proxy_docker/app/data/sqlmigrate20210808_0.7.0-0.8.0.sql b/proxy_docker/app/data/sqlmigrate20210808_0.7.0-0.8.0.sql new file mode 100644 index 0000000..61ba781 --- /dev/null +++ b/proxy_docker/app/data/sqlmigrate20210808_0.7.0-0.8.0.sql @@ -0,0 +1,9 @@ +PRAGMA foreign_keys=off; + +BEGIN TRANSACTION; + +ALTER TABLE watching ADD COLUMN label TEXT; + +COMMIT; + +PRAGMA foreign_keys=on; diff --git a/proxy_docker/app/script/getactivewatches.sh b/proxy_docker/app/script/getactivewatches.sh index ad40fb7..7cb25a3 100644 --- a/proxy_docker/app/script/getactivewatches.sh +++ b/proxy_docker/app/script/getactivewatches.sh @@ -67,7 +67,7 @@ getactivewatches() { local watches # Let's build the string directly with sqlite instead of manipulating multiple strings afterwards, it's faster. # {"id":"${id}","address":"${address}","imported":"${imported}","unconfirmedCallbackURL":"${cb0conf_url}","confirmedCallbackURL":"${cb1conf_url}","watching_since":"${timestamp}"} - watches=$(sql "SELECT '{\"id\":' || id || ',\"address\":\"' || address || '\",\"imported\":' || imported || ',\"unconfirmedCallbackURL\":\"' || COALESCE(callback0conf, '') || '\",\"confirmedCallbackURL\":\"' || COALESCE(callback1conf, '') || '\",\"watching_since\":\"' || inserted_ts || '\"}' FROM watching WHERE watching AND NOT calledback1conf") + watches=$(sql "SELECT '{\"id\":' || id || ',\"address\":\"' || address || '\",\"imported\":' || imported || ',\"unconfirmedCallbackURL\":\"' || COALESCE(callback0conf, '') || '\",\"confirmedCallbackURL\":\"' || COALESCE(callback1conf, '') || '\",\"label\":\"' || COALESCE(label, '') || '\",\"watching_since\":\"' || inserted_ts || '\"}' FROM watching WHERE watching AND NOT calledback1conf") returncode=$? trace_rc ${returncode} diff --git a/proxy_docker/app/script/importaddress.sh b/proxy_docker/app/script/importaddress.sh index c2721e2..8b2128c 100644 --- a/proxy_docker/app/script/importaddress.sh +++ b/proxy_docker/app/script/importaddress.sh @@ -7,7 +7,12 @@ importaddress_rpc() { trace "[Entering importaddress_rpc()]" local address=${1} - local data="{\"method\":\"importaddress\",\"params\":[\"${address}\",\"\",false]}" + local label=${2} + if [ -z "${label}" ]; then + label="null" + fi + local data='{"method":"importaddress","params":{"address":"'${address}'","label":'${label}',"rescan":false}}' + # local data="{\"method\":\"importaddress\",\"params\":[\"${address}\",\"\",false]}" local result result=$(send_to_watcher_node ${data}) local returncode=$? diff --git a/proxy_docker/app/script/manage_missed_conf.sh b/proxy_docker/app/script/manage_missed_conf.sh index fe700db..ec202b8 100644 --- a/proxy_docker/app/script/manage_missed_conf.sh +++ b/proxy_docker/app/script/manage_missed_conf.sh @@ -11,15 +11,17 @@ manage_not_imported() { trace "[Entering manage_not_imported()]" - local watches=$(sql 'SELECT address FROM watching WHERE watching AND NOT imported') + local watches=$(sql 'SELECT address, label FROM watching WHERE watching AND NOT imported') trace "[manage_not_imported] watches=${watches}" local result local returncode local IFS=$'\n' - for address in ${watches} + for row in ${watches} do - result=$(importaddress_rpc "${address}") + address=$(echo "${row}" | cut -d '|' -f1) + label=$(echo "${row}" | cut -d '|' -f2) + result=$(importaddress_rpc "${address}" "${label}") returncode=$? trace_rc ${returncode} if [ "${returncode}" -eq 0 ]; then diff --git a/proxy_docker/app/script/requesthandler.sh b/proxy_docker/app/script/requesthandler.sh index 8d6e1e1..2ca8837 100644 --- a/proxy_docker/app/script/requesthandler.sh +++ b/proxy_docker/app/script/requesthandler.sh @@ -93,6 +93,7 @@ main() { # POST http://192.168.111.152:8080/watch # BODY {"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","unconfirmedCallbackURL":"192.168.111.233:1111/callback0conf","confirmedCallbackURL":"192.168.111.233:1111/callback1conf"} # BODY {"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","confirmedCallbackURL":"192.168.111.233:1111/callback1conf","eventMessage":"eyJib3VuY2VfYWRkcmVzcyI6IjJNdkEzeHIzOHIxNXRRZWhGblBKMVhBdXJDUFR2ZTZOamNGIiwibmJfY29uZiI6MH0K"} + # BODY {"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","confirmedCallbackURL":"192.168.111.233:1111/callback1conf","eventMessage":"eyJib3VuY2VfYWRkcmVzcyI6IjJNdkEzeHIzOHIxNXRRZWhGblBKMVhBdXJDUFR2ZTZOamNGIiwibmJfY29uZiI6MH0K","label":"myLabel"} response=$(watchrequest "${line}") response_to_client "${response}" ${?} @@ -328,8 +329,23 @@ main() { getnewaddress) # curl (GET) http://192.168.111.152:8080/getnewaddress # curl (GET) http://192.168.111.152:8080/getnewaddress/bech32 + # + # or... + # POST http://192.168.111.152:8080/getnewaddress + # BODY {"address_type":"bech32","label":"myLabel"} + # BODY {"label":"myLabel"} + # BODY {"address_type":"p2sh-segwit"} + # BODY {} - response=$(getnewaddress "$(echo "${line}" | cut -d ' ' -f2 | cut -d '/' -f3)") + # Let's make it work even for a GET request (equivalent to a POST with empty json object body) + if [ "$http_method" = "POST" ]; then + address_type=$(echo "${line}" | jq -er ".addressType // empty") + label=$(echo "${line}" | jq -er ".label // empty") + else + address_type=$(echo "${line}" | cut -d ' ' -f2 | cut -d '/' -f3) + fi + + response=$(getnewaddress "${address_type}" "${label}") response_to_client "${response}" ${?} break ;; diff --git a/proxy_docker/app/script/walletoperations.sh b/proxy_docker/app/script/walletoperations.sh index 4975238..4165c7a 100644 --- a/proxy_docker/app/script/walletoperations.sh +++ b/proxy_docker/app/script/walletoperations.sh @@ -270,13 +270,29 @@ getnewaddress() { local address_type=${1} trace "[getnewaddress] address_type=${address_type}" + local label=${2} + trace "[getnewaddress] label=${label}" + local response - local data - if [ -z "${address_type}" ]; then - data='{"method":"getnewaddress"}' - else - data="{\"method\":\"getnewaddress\",\"params\":[\"\",\"${address_type}\"]}" + local jqop + local addedfields + local data='{"method":"getnewaddress"}' + if [ -n "${address_type}" ] || [ -n "${label}" ]; then + jqop='. += {"params":{}}' + if [ -n "${label}" ]; then + jqop=${jqop}' | .params += {"label":"'${label}'"}' + addedfields=' | . += {"label":"'${label}'"}' + fi + if [ -n "${address_type}" ]; then + jqop=${jqop}' | .params += {"address_type":"'${address_type}'"}' + addedfields=' | . += {"address_type":"'${address_type}'"}' + fi + trace "[getnewaddress] jqop=${jqop}" + + data=$(echo "${data}" | jq -rc "${jqop}") fi + trace "[getnewaddress] data=${data}" + response=$(send_to_spender_node "${data}") local returncode=$? trace_rc ${returncode} @@ -286,7 +302,11 @@ getnewaddress() { local address=$(echo ${response} | jq ".result") trace "[getnewaddress] address=${address}" - data="{\"address\":${address}}" + data='{"address":'${address}'}' + if [ -n "${jqop}" ]; then + data=$(echo "${data}" | jq -rc "${data}${addedfields}") + trace "[getnewaddress] data=${data}" + fi else trace "[getnewaddress] Coudn't get a new address!" data="" diff --git a/proxy_docker/app/script/watchrequest.sh b/proxy_docker/app/script/watchrequest.sh index e4aee18..04d97b7 100644 --- a/proxy_docker/app/script/watchrequest.sh +++ b/proxy_docker/app/script/watchrequest.sh @@ -15,6 +15,7 @@ watchrequest() { local cb0conf_url=$(echo "${request}" | jq ".unconfirmedCallbackURL") local cb1conf_url=$(echo "${request}" | jq ".confirmedCallbackURL") local event_message=$(echo "${request}" | jq ".eventMessage") + local label=$(echo "${request}" | jq ".label") local imported local inserted local id_inserted @@ -23,7 +24,7 @@ watchrequest() { # Let's lowercase bech32 addresses address=$(lowercase_if_bech32 "${address}") - trace "[watchrequest] Watch request on address (\"${address}\"), cb 0-conf (${cb0conf_url}), cb 1-conf (${cb1conf_url}) with event_message=${event_message}" + trace "[watchrequest] Watch request on address (\"${address}\"), cb 0-conf (${cb0conf_url}), cb 1-conf (${cb1conf_url}) with event_message=${event_message} and label=${label}" local isvalid isvalid=$(validateaddress "${address}" | jq ".result.isvalid") @@ -38,6 +39,7 @@ watchrequest() { \"address\":\"${address}\", \"unconfirmedCallbackURL\":${cb0conf_url}, \"confirmedCallbackURL\":${cb1conf_url}, + \"label\":${label}, \"eventMessage\":${event_message}}}}" trace "[watchrequest] Invalid address" trace "[watchrequest] responding=${result}" @@ -47,7 +49,7 @@ watchrequest() { return 1 fi - result=$(importaddress_rpc ${address}) + result=$(importaddress_rpc "${address}" "${label}") returncode=$? trace_rc ${returncode} if [ "${returncode}" -eq 0 ]; then @@ -56,7 +58,7 @@ watchrequest() { imported=0 fi - sql "INSERT INTO watching (address, watching, callback0conf, callback1conf, imported, event_message) VALUES (\"${address}\", 1, ${cb0conf_url}, ${cb1conf_url}, ${imported}, ${event_message}) ON CONFLICT(address,callback0conf,callback1conf) DO UPDATE SET watching=1, event_message=${event_message}, calledback0conf=0, calledback1conf=0" + sql "INSERT INTO watching (address, watching, callback0conf, callback1conf, imported, event_message, label) VALUES (\"${address}\", 1, ${cb0conf_url}, ${cb1conf_url}, ${imported}, ${event_message}, ${label}) ON CONFLICT(address,callback0conf,callback1conf) DO UPDATE SET watching=1, event_message=${event_message}, calledback0conf=0, calledback1conf=0, label=${label}" returncode=$? trace_rc ${returncode} @@ -88,6 +90,7 @@ watchrequest() { \"address\":\"${address}\", \"unconfirmedCallbackURL\":${cb0conf_url}, \"confirmedCallbackURL\":${cb1conf_url}, + \"label\":${label}, \"estimatesmartfee2blocks\":${fees2blocks}, \"estimatesmartfee6blocks\":${fees6blocks}, \"estimatesmartfee36blocks\":${fees36blocks},