Support for Bitcoin Core labels for watched and new addresses

This commit is contained in:
kexkey
2021-08-08 14:47:28 -04:00
parent 80f17d9176
commit 2e60b91a6a
10 changed files with 170 additions and 18 deletions

View File

@@ -9,7 +9,7 @@ Inserts the address, webhook URLs and eventMessage in the DB and imports the add
```http ```http
POST http://cyphernode:8888/watch POST http://cyphernode:8888/watch
with body... 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: Proxy response:
@@ -23,6 +23,7 @@ Proxy response:
"address": "2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp", "address": "2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp",
"unconfirmedCallbackURL": "192.168.133.233:1111/callback0conf", "unconfirmedCallbackURL": "192.168.133.233:1111/callback0conf",
"confirmedCallbackURL": "192.168.133.233:1111/callback1conf", "confirmedCallbackURL": "192.168.133.233:1111/callback1conf",
"label": "myLabel",
"estimatesmartfee2blocks": "0.000010", "estimatesmartfee2blocks": "0.000010",
"estimatesmartfee6blocks": "0.000010", "estimatesmartfee6blocks": "0.000010",
"estimatesmartfee36blocks": "0.000010", "estimatesmartfee36blocks": "0.000010",
@@ -721,6 +722,20 @@ GET http://cyphernode:8888/getnewaddress/legacy
GET http://cyphernode:8888/getnewaddress/p2sh-segwit 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: Proxy response:
```json ```json
@@ -731,7 +746,9 @@ Proxy response:
```json ```json
{ {
"address":"tb1ql7yvh3lmajxmaljsnsu3w8lhwczu963tvjfzpj" "address":"tb1ql7yvh3lmajxmaljsnsu3w8lhwczu963tvjfzpj",
"label":"myLabel",
"address_type":"bech32"
} }
``` ```

View File

@@ -62,6 +62,9 @@ paths:
eventMessage: eventMessage:
description: "Will be part of the published message on confirmations" description: "Will be part of the published message on confirmations"
type: "string" type: "string"
label:
description: "Label for this address that will be imported in Bitcoin Core"
type: "string"
responses: responses:
'200': '200':
description: "successfully created" description: "successfully created"
@@ -1096,8 +1099,16 @@ paths:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/ApiResponseTemporarilyUnavailable' $ref: '#/components/schemas/ApiResponseTemporarilyUnavailable'
/getnewaddress: /getnewaddress/{address_type}:
get: get:
parameters:
- in: "path"
name: "address_type"
description: "Address type"
required: false
schema:
type: "string"
enum: ["legacy", "p2sh-segwit", "bech32"]
tags: tags:
- "spending wallet" - "spending wallet"
- "core features" - "core features"
@@ -1114,6 +1125,57 @@ paths:
properties: properties:
address: address:
$ref: '#/components/schemas/TypeAddressString' $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': '403':
$ref: '#/components/schemas/ApiResponseNotAllowed' $ref: '#/components/schemas/ApiResponseNotAllowed'
'503': '503':
@@ -2491,6 +2553,7 @@ components:
- "unconfirmedCallbackURL" - "unconfirmedCallbackURL"
- "confirmedCallbackURL" - "confirmedCallbackURL"
- "eventMessage" - "eventMessage"
- "label"
properties: properties:
id: id:
type: "string" type: "string"
@@ -2512,6 +2575,9 @@ components:
description: "Async callback in case of activity on address" description: "Async callback in case of activity on address"
type: "string" type: "string"
format: "url" format: "url"
label:
description: "Label for this address that will be imported in Bitcoin Core"
type: "string"
estimatesmartfee2blocks: estimatesmartfee2blocks:
type: "string" type: "string"
estimatesmartfee6blocks: estimatesmartfee6blocks:

View File

@@ -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

View File

@@ -0,0 +1,9 @@
PRAGMA foreign_keys=off;
BEGIN TRANSACTION;
ALTER TABLE watching ADD COLUMN label TEXT;
COMMIT;
PRAGMA foreign_keys=on;

View File

@@ -67,7 +67,7 @@ getactivewatches() {
local watches local watches
# Let's build the string directly with sqlite instead of manipulating multiple strings afterwards, it's faster. # 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}"} # {"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=$? returncode=$?
trace_rc ${returncode} trace_rc ${returncode}

View File

@@ -7,7 +7,12 @@ importaddress_rpc() {
trace "[Entering importaddress_rpc()]" trace "[Entering importaddress_rpc()]"
local address=${1} 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 local result
result=$(send_to_watcher_node ${data}) result=$(send_to_watcher_node ${data})
local returncode=$? local returncode=$?

View File

@@ -11,15 +11,17 @@ manage_not_imported() {
trace "[Entering 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}" trace "[manage_not_imported] watches=${watches}"
local result local result
local returncode local returncode
local IFS=$'\n' local IFS=$'\n'
for address in ${watches} for row in ${watches}
do do
result=$(importaddress_rpc "${address}") address=$(echo "${row}" | cut -d '|' -f1)
label=$(echo "${row}" | cut -d '|' -f2)
result=$(importaddress_rpc "${address}" "${label}")
returncode=$? returncode=$?
trace_rc ${returncode} trace_rc ${returncode}
if [ "${returncode}" -eq 0 ]; then if [ "${returncode}" -eq 0 ]; then

View File

@@ -93,6 +93,7 @@ main() {
# POST http://192.168.111.152:8080/watch # 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","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"}
# BODY {"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","confirmedCallbackURL":"192.168.111.233:1111/callback1conf","eventMessage":"eyJib3VuY2VfYWRkcmVzcyI6IjJNdkEzeHIzOHIxNXRRZWhGblBKMVhBdXJDUFR2ZTZOamNGIiwibmJfY29uZiI6MH0K","label":"myLabel"}
response=$(watchrequest "${line}") response=$(watchrequest "${line}")
response_to_client "${response}" ${?} response_to_client "${response}" ${?}
@@ -328,8 +329,23 @@ main() {
getnewaddress) getnewaddress)
# curl (GET) http://192.168.111.152:8080/getnewaddress # curl (GET) http://192.168.111.152:8080/getnewaddress
# curl (GET) http://192.168.111.152:8080/getnewaddress/bech32 # 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}" ${?} response_to_client "${response}" ${?}
break break
;; ;;

View File

@@ -270,13 +270,29 @@ getnewaddress() {
local address_type=${1} local address_type=${1}
trace "[getnewaddress] address_type=${address_type}" trace "[getnewaddress] address_type=${address_type}"
local label=${2}
trace "[getnewaddress] label=${label}"
local response local response
local data local jqop
if [ -z "${address_type}" ]; then local addedfields
data='{"method":"getnewaddress"}' local data='{"method":"getnewaddress"}'
else if [ -n "${address_type}" ] || [ -n "${label}" ]; then
data="{\"method\":\"getnewaddress\",\"params\":[\"\",\"${address_type}\"]}" jqop='. += {"params":{}}'
if [ -n "${label}" ]; then
jqop=${jqop}' | .params += {"label":"'${label}'"}'
addedfields=' | . += {"label":"'${label}'"}'
fi 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}") response=$(send_to_spender_node "${data}")
local returncode=$? local returncode=$?
trace_rc ${returncode} trace_rc ${returncode}
@@ -286,7 +302,11 @@ getnewaddress() {
local address=$(echo ${response} | jq ".result") local address=$(echo ${response} | jq ".result")
trace "[getnewaddress] address=${address}" 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 else
trace "[getnewaddress] Coudn't get a new address!" trace "[getnewaddress] Coudn't get a new address!"
data="" data=""

View File

@@ -15,6 +15,7 @@ watchrequest() {
local cb0conf_url=$(echo "${request}" | jq ".unconfirmedCallbackURL") local cb0conf_url=$(echo "${request}" | jq ".unconfirmedCallbackURL")
local cb1conf_url=$(echo "${request}" | jq ".confirmedCallbackURL") local cb1conf_url=$(echo "${request}" | jq ".confirmedCallbackURL")
local event_message=$(echo "${request}" | jq ".eventMessage") local event_message=$(echo "${request}" | jq ".eventMessage")
local label=$(echo "${request}" | jq ".label")
local imported local imported
local inserted local inserted
local id_inserted local id_inserted
@@ -23,7 +24,7 @@ watchrequest() {
# Let's lowercase bech32 addresses # Let's lowercase bech32 addresses
address=$(lowercase_if_bech32 "${address}") 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 local isvalid
isvalid=$(validateaddress "${address}" | jq ".result.isvalid") isvalid=$(validateaddress "${address}" | jq ".result.isvalid")
@@ -38,6 +39,7 @@ watchrequest() {
\"address\":\"${address}\", \"address\":\"${address}\",
\"unconfirmedCallbackURL\":${cb0conf_url}, \"unconfirmedCallbackURL\":${cb0conf_url},
\"confirmedCallbackURL\":${cb1conf_url}, \"confirmedCallbackURL\":${cb1conf_url},
\"label\":${label},
\"eventMessage\":${event_message}}}}" \"eventMessage\":${event_message}}}}"
trace "[watchrequest] Invalid address" trace "[watchrequest] Invalid address"
trace "[watchrequest] responding=${result}" trace "[watchrequest] responding=${result}"
@@ -47,7 +49,7 @@ watchrequest() {
return 1 return 1
fi fi
result=$(importaddress_rpc ${address}) result=$(importaddress_rpc "${address}" "${label}")
returncode=$? returncode=$?
trace_rc ${returncode} trace_rc ${returncode}
if [ "${returncode}" -eq 0 ]; then if [ "${returncode}" -eq 0 ]; then
@@ -56,7 +58,7 @@ watchrequest() {
imported=0 imported=0
fi 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=$? returncode=$?
trace_rc ${returncode} trace_rc ${returncode}
@@ -88,6 +90,7 @@ watchrequest() {
\"address\":\"${address}\", \"address\":\"${address}\",
\"unconfirmedCallbackURL\":${cb0conf_url}, \"unconfirmedCallbackURL\":${cb0conf_url},
\"confirmedCallbackURL\":${cb1conf_url}, \"confirmedCallbackURL\":${cb1conf_url},
\"label\":${label},
\"estimatesmartfee2blocks\":${fees2blocks}, \"estimatesmartfee2blocks\":${fees2blocks},
\"estimatesmartfee6blocks\":${fees6blocks}, \"estimatesmartfee6blocks\":${fees6blocks},
\"estimatesmartfee36blocks\":${fees36blocks}, \"estimatesmartfee36blocks\":${fees36blocks},