diff --git a/api_auth_docker/api-sample.properties b/api_auth_docker/api-sample.properties index 7e31558..b3d6f88 100644 --- a/api_auth_docker/api-sample.properties +++ b/api_auth_docker/api-sample.properties @@ -9,6 +9,7 @@ action_unwatchxpubbylabel=watcher action_getactivewatchesbyxpub=watcher action_getactivewatchesbylabel=watcher action_getactivexpubwatches=watcher +action_watchtxid=watcher action_getactivewatches=watcher action_getbestblockhash=watcher action_getbestblockinfo=watcher @@ -34,11 +35,13 @@ action_ln_newaddr=spender action_ots_stamp=spender action_ots_getfile=spender action_ln_getinvoice=spender +action_ln_connectfund=spender # Admin can do what the spender can do, plus: # Should be called from inside the Docker network only: action_conf=internal +action_newblock=internal action_executecallbacks=internal action_ots_backoffice=internal diff --git a/install/generator-cyphernode/generators/app/index.js b/install/generator-cyphernode/generators/app/index.js index 7e31027..cdcd4ea 100644 --- a/install/generator-cyphernode/generators/app/index.js +++ b/install/generator-cyphernode/generators/app/index.js @@ -32,6 +32,7 @@ action_unwatchxpubbylabel=watcher action_getactivewatchesbyxpub=watcher action_getactivewatchesbylabel=watcher action_getactivexpubwatches=watcher +action_watchtxid=watcher action_getactivewatches=watcher action_getbestblockhash=watcher action_getbestblockinfo=watcher @@ -58,12 +59,14 @@ action_ots_stamp=spender action_ots_getfile=spender action_ln_getinvoice=spender action_ln_decodebolt11=spender +action_ln_connectfund=spender # Admin can do what the spender can do, plus: # Should be called from inside the Docker network only: action_conf=internal +action_newblock=internal action_executecallbacks=internal action_ots_backoffice=internal `; diff --git a/install/generator-cyphernode/generators/app/templates/bitcoin/bitcoin.conf b/install/generator-cyphernode/generators/app/templates/bitcoin/bitcoin.conf index f1000e2..0f6af2d 100644 --- a/install/generator-cyphernode/generators/app/templates/bitcoin/bitcoin.conf +++ b/install/generator-cyphernode/generators/app/templates/bitcoin/bitcoin.conf @@ -39,6 +39,7 @@ main.wallet=ln01.dat <% } %> walletnotify=/usr/bin/curl proxy:8888/conf/%s +blocknotify=/usr/bin/curl proxy:8888/newblock/%s <% if ( bitcoin_uacomment != null && bitcoin_uacomment != '' ) { %> uacomment=<%= bitcoin_uacomment %> diff --git a/install/generator-cyphernode/generators/app/templates/installer/testfeatures.sh b/install/generator-cyphernode/generators/app/templates/installer/testfeatures.sh index 2b02d1a..676360c 100644 --- a/install/generator-cyphernode/generators/app/templates/installer/testfeatures.sh +++ b/install/generator-cyphernode/generators/app/templates/installer/testfeatures.sh @@ -336,7 +336,7 @@ result="${result}$(feature_status ${returncode} 'Lightning error!')}" result="${result},{\"name\":\"sparkwallet\",\"working\":" status=$(echo "{${containers}}" | jq ".containers[] | select(.name == \"sparkwallet\") | .active") if [[ "${brokenproxy}" != "true" && "${status}" = "true" ]]; then - timeout_feature checklnnode + timeout_feature checksparkwallet returncode=$? else returncode=1 diff --git a/proxy_docker/app/data/cyphernode.sql b/proxy_docker/app/data/cyphernode.sql index 6cca484..97aeef0 100644 --- a/proxy_docker/app/data/cyphernode.sql +++ b/proxy_docker/app/data/cyphernode.sql @@ -67,6 +67,19 @@ CREATE TABLE recipient ( ); CREATE INDEX idx_recipient_address ON recipient (address); +CREATE TABLE watching_by_txid ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + txid TEXT UNIQUE, + watching INTEGER DEFAULT FALSE, + callback1conf TEXT, + calledback1conf INTEGER DEFAULT FALSE, + callbackxconf TEXT, + calledbackxconf INTEGER DEFAULT FALSE, + nbxconf INTEGER, + inserted_ts INTEGER DEFAULT CURRENT_TIMESTAMP +); +CREATE INDEX idx_watching_by_txid_txid ON watching_by_txid (txid); + CREATE TABLE stamp ( id INTEGER PRIMARY KEY AUTOINCREMENT, hash TEXT UNIQUE, diff --git a/proxy_docker/app/data/sqlmigrate20190104_0.1-0.2.sql b/proxy_docker/app/data/sqlmigrate20190104_0.1-0.2.sql index 4d6e588..20b2528 100644 --- a/proxy_docker/app/data/sqlmigrate20190104_0.1-0.2.sql +++ b/proxy_docker/app/data/sqlmigrate20190104_0.1-0.2.sql @@ -21,3 +21,16 @@ CREATE TABLE ln_invoice ( ); CREATE INDEX idx_lninvoice_label ON ln_invoice (label); CREATE INDEX idx_lninvoice_bolt11 ON ln_invoice (bolt11); + +CREATE TABLE watching_by_txid ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + txid TEXT UNIQUE, + watching INTEGER DEFAULT FALSE, + callback1conf TEXT, + calledback1conf INTEGER DEFAULT FALSE, + callbackxconf TEXT, + calledbackxconf INTEGER DEFAULT FALSE, + nbxconf INTEGER, + inserted_ts INTEGER DEFAULT CURRENT_TIMESTAMP +); +CREATE INDEX idx_watching_by_txid_txid ON watching_by_txid (txid); diff --git a/proxy_docker/app/script/call_lightningd.sh b/proxy_docker/app/script/call_lightningd.sh index f1a7f39..722b371 100644 --- a/proxy_docker/app/script/call_lightningd.sh +++ b/proxy_docker/app/script/call_lightningd.sh @@ -75,13 +75,13 @@ ln_create_invoice() ln_get_connection_string() { trace "Entering ln_get_connection_string()..." - + echo "{\"connectstring\":\"$(get_connection_string)\"}" } get_connection_string() { trace "Entering get_connection_string()..." - + # Let's get the connect string if provided in configuration local connectstring local getinfo=$(ln_getinfo) @@ -140,7 +140,7 @@ ln_delinvoice() { returncode=$? trace_rc ${returncode} trace "[ln_delinvoice] result=${result}" - + if [ "${returncode}" -ne "0" ]; then # Special case of error: if status is expired, we're ok echo "${result}" | grep "not unpaid" > /dev/null @@ -175,6 +175,80 @@ ln_decodebolt11() { return ${returncode} } +ln_connectfund() { + trace "Entering ln_connectfund()..." + + local result + local returncode + local tx + local txid + local nodeId + + local request=${1} + local peer=$(echo "${request}" | jq ".peer" | tr -d '"') + trace "[ln_connectfund] peer=${peer}" + local msatoshi=$(echo "${request}" | jq ".msatoshi") + trace "[ln_connectfund] msatoshi=${msatoshi}" + local callback_url=$(echo "${request}" | jq ".callbackUrl" | tr -d '"') + trace "[ln_connectfund] callback_url=${callback_url}" + + # Let's first try to connect to peer + trace "[ln_connectfund] ./lightning-cli connect ${peer}" + result=$(./lightning-cli connect ${peer}) + returncode=$? + trace_rc ${returncode} + trace "[ln_connectfund] result=${result}" + + if [ "${returncode}" -eq "0" ]; then + # Connected + +# ./lightning-cli connect 038863cf8ab91046230f561cd5b386cbff8309fa02e3f0c3ed161a3aeb64a643b9@180.181.208.42:9735 +# { +# "id": "038863cf8ab91046230f561cd5b386cbff8309fa02e3f0c3ed161a3aeb64a643b9" +# } + +# ./lightning-cli connect 021a1b197aa79242532b23cb9a8d9cb78631f95f811457675fa1b362fe6d1c24b8@172.81.180.244:9735 +# { "code" : -1, "message" : "172.1.180.244:9735: Connection establishment: Operation timed out. " } + + nodeId=$(echo "${result}" | jq ".id" | tr -d '"') + trace "[ln_connectfund] nodeId=${nodeId}" + + # Now let's fund a channel with peer + trace "[ln_connectfund] ./lightning-cli fundchannel ${nodeId} ${msatoshi}" + result=$(./lightning-cli fundchannel ${nodeId} ${msatoshi}) + returncode=$? + trace_rc ${returncode} + trace "[ln_connectfund] result=${result}" + + if [ "${returncode}" -eq "0" ]; then + # funding succeeded + +# ./lightning-cli fundchannel 038863cf8ab91046230f561cd5b386cbff8309fa02e3f0c3ed161a3aeb64a643b9 1000000 +# { +# "tx": "020000000001011594f707cf2ec076278072bc64f893bbd70188db42ea49e9ba531ee3c7bc8ed00100000000ffffffff0240420f00000000002200206149ff97921356191dc1f2e9ab997c459a71e8050d272721abf4b4d8a92d2419a6538900000000001600142cab0184d0f8098f75ebe05172b5864395e033f402483045022100b25cd5a9d49b5cc946f72a58ccc0afe652d99c25fba98d68be035a286f55849802203de5b504c44f775a0101b6025f116b73bf571e776e4efcac0475721bfde4d08a0121038360308a394158b0799196c5179a6480a75db73207fb93d4a673d934c9f786f400000000", +# "txid": "747bf7d1c40bebed578b3f02a3d8da9a56885851a3c4bdb6e1b8de19223559a4", +# "channel_id": "a459352219deb8e1b6bdc4a3515888569adad8a3023f8b57edeb0bc4d1f77b74" +# } + +# ./lightning-cli fundchannel 038863cf8ab91046230f561cd5b386cbff8309fa02e3f0c3ed161a3aeb64a643b9 100000 +# { "code" : 301, "message" : "Cannot afford transaction" } + + # Let's find what to watch + txid=$(echo "${result}" | jq ".txid" | tr -d '"') + tx=$(echo "${result}" | jq ".tx" | tr -d '"') + + + else + # Error funding + trace "[ln_connectfund] Error funding, result=${result}" + fi + else + # Error connecting + trace "[ln_connectfund] Error connecting, result=${result}" + fi + +} + ln_pay() { trace "Entering ln_pay()..." @@ -202,7 +276,7 @@ ln_pay() { returncode=$? trace_rc ${returncode} trace "[ln_pay] result=${result}" - + if [ "${returncode}" -eq "0" ]; then local invoice_msatoshi=$(echo "${result}" | jq ".msatoshi") trace "[ln_pay] invoice_msatoshi=${invoice_msatoshi}" @@ -236,13 +310,13 @@ ln_pay() { if [ "${returncode}" -ne "0" ]; then trace "[ln_pay] payment not complete, let's see what's going on." - + code=$(echo "${result}" | jq -e ".code") # jq -e will have a return code of 1 if the supplied tag is null. if [ "$?" -eq "0" ]; then # code tag not null, so there's an error trace "[ln_pay] Error code found, code=${code}" - + if [ "${code}" -eq "200" ]; then trace "[ln_pay] Code 200, let's fetch status in data, should be pending..." status=$(echo "${result}" | jq ".data.status" | tr -d '"') @@ -279,6 +353,43 @@ ln_pay() { fi fi +# Example of error result: +# +# { "code" : 204, "message" : "failed: WIRE_TEMPORARY_CHANNEL_FAILURE (Outgoing subdaemon died)", "data" : +# { +# "erring_index": 0, +# "failcode": 4103, +# "erring_node": "031b867d9d6631a1352cc0f37bcea94bd5587a8d4f40416c4ce1a12511b1e68f56", +# "erring_channel": "1452982:62:0" +# } } +# +# +# Example of successful result: +# +# { +# "id": 44, +# "payment_hash": "de648062da7117903291dab2075881e49ddd78efbf82438e4a2f486a7ebe0f3a", +# "destination": "02be93d1dad1ccae7beea7b42f8dbcfbdafb4d342335c603125ef518200290b450", +# "msatoshi": 207000, +# "msatoshi_sent": 207747, +# "created_at": 1548380406, +# "status": "complete", +# "payment_preimage": "a7ef27e9a94d63e4028f35ca4213fd9008227ad86815cd40d3413287d819b145", +# "description": "Order 43012 - Satoshi Larrivee", +# "getroute_tries": 1, +# "sendpay_tries": 1, +# "route": [ +# { +# "id": "02be93d1dad1ccae7beea7b42f8dbcfbdafb4d342335c603125ef518200290b450", +# "channel": "1452749:174:0", +# "msatoshi": 207747, +# "delay": 10 +# } +# ], +# "failures": [ +# ] +# } + echo "${result}" return ${returncode} diff --git a/proxy_docker/app/script/callbacks_txid.sh b/proxy_docker/app/script/callbacks_txid.sh new file mode 100644 index 0000000..4137486 --- /dev/null +++ b/proxy_docker/app/script/callbacks_txid.sh @@ -0,0 +1,129 @@ +#!/bin/sh + +. ./trace.sh +. ./sql.sh + +do_callbacks_txid() { + ( + flock -x 200 || return 0 + + trace "Entering do_callbacks_txid()..." + + # Let's fetch all the watching txid still being watched but not called back + local callbacks=$(sql 'SELECT id, txid, callback1conf, 1 FROM watching_by_txid WHERE watching AND callback1conf NOT NULL AND NOT calledback1conf') + trace "[do_callbacks_txid] callbacks1conf=${callbacks}" + + local returncode + local address + local url + local IFS=$'\n' + for row in ${callbacks} + do + build_callback_txid ${row} + returncode=$? + trace_rc ${returncode} + if [ "${returncode}" -eq 0 ]; then + txid=$(echo "${row}" | cut -d '|' -f2) + sql "UPDATE watching_by_txid SET calledback1conf=1 WHERE txid=\"${txid}\"" + trace_rc $? + fi + done + + local callbacks=$(sql 'SELECT id, txid, callbackxconf, nbxconf FROM watching_by_txid WHERE watching AND calledback1conf AND callbackxconf NOT NULL AND NOT calledbackxconf') + trace "[do_callbacks_txid] callbacksxconf=${callbacks}" + + for row in ${callbacks} + do + build_callback_txid ${row} + returncode=$? + if [ "${returncode}" -eq 0 ]; then + txid=$(echo "${row}" | cut -d '|' -f2) + sql "UPDATE watching_by_txid SET calledbackxconf=1, watching=0 WHERE txid=\"${txid}\"" + trace_rc $? + fi + done + + ) 200>./.callbacks.lock +} + +build_callback_txid() { + trace "Entering build_callback_txid()..." + + local row=$@ + local id + local txid + local url + local nbxconf + local blockhash + local blockheight + local confirmations + local data + local tx_raw_details + + # id, txid, url, nbconf + + trace "[build_callback_txid] row=${row}" + id=$(echo "${row}" | cut -d '|' -f1) + trace "[build_callback_txid] id=${id}" + txid=$(echo "${row}" | cut -d '|' -f2) + trace "[build_callback_txid] txid=${txid}" + url=$(echo "${row}" | cut -d '|' -f3) + trace "[build_callback_txid] url=${url}" + nbxconf=$(echo "${row}" | cut -d '|' -f4) + trace "[build_callback_txid] nbxconf=${nbxconf}" + + tx_raw_details=$(get_rawtransaction ${txid}) + returncode=$? + trace_rc ${returncode} + + if [ "${returncode}" -eq "0" ]; then + confirmations=$(echo "${tx_raw_details}" | jq '.result.confirmations') + trace "[build_callback_txid] confirmations=${confirmations}" + + if [ "${confirmations}" -ge "${nbxconf}" ]; then + trace "[build_callback_txid] Number of confirmations for tx is at least what we're looking for, callback time!" + # Number of confirmations for transaction is at least what we want + # Let's prepare the callback! + + # blockhash=$(echo "${tx_raw_details}" | jq '.result.blockhash') + # trace "[build_callback_txid] blockhash=${blockhash}" + # blockheight=$(get_block_info $(echo "${blockhash}" | tr -d '"') | jq '.result.height') + # trace "[build_callback_txid] blockheight=${blockheight}" + + data="{\"id\":\"${id}\"," + data="${data}\"txid\":\"${txid}\"," + data="${data}\"confirmations\":${confirmations}" + data="${data}}" + trace "[build_callback_txid] data=${data}" + + curl_callback_txid "${url}" "${data}" + return $? + else + trace "[build_callback_txid] Number of confirmations for tx is not enough to call back." + fi + fi +} + +curl_callback_txid() { + trace "Entering curl_callback_txid()..." + + local url=${1} + local data=${2} + local returncode + + trace "[curl_callback_txid] curl -w \"%{http_code}\" -H \"Content-Type: application/json\" -H \"X-Forwarded-Proto: https\" -d \"${data}\" ${url}" + rc=$(curl -w "%{http_code}" -H "Content-Type: application/json" -H "X-Forwarded-Proto: https" -d "${data}" ${url}) + returncode=$? + trace "[curl_callback_txid] HTTP return code=${rc}" + trace_rc ${returncode} + + if [ "${returncode}" -eq "0" ]; then + if [ "${rc}" -lt "400" ]; then + return 0 + else + return ${rc} + fi + else + return ${returncode} + fi +} diff --git a/proxy_docker/app/script/newblock.sh b/proxy_docker/app/script/newblock.sh new file mode 100644 index 0000000..c266eeb --- /dev/null +++ b/proxy_docker/app/script/newblock.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +. ./trace.sh +. ./callbacks_txid.sh + +newblock() { + trace "Entering newblock()..." + + local request=${1} + local blockhash=$(echo "${request}" | cut -d ' ' -f2 | cut -d '/' -f3) + + do_callbacks_txid +} diff --git a/proxy_docker/app/script/requesthandler.sh b/proxy_docker/app/script/requesthandler.sh index ebe3058..61fe97c 100644 --- a/proxy_docker/app/script/requesthandler.sh +++ b/proxy_docker/app/script/requesthandler.sh @@ -18,6 +18,7 @@ . ./bitcoin.sh . ./call_lightningd.sh . ./ots.sh +. ./newblock.sh main() { @@ -128,6 +129,11 @@ main() # GET http://192.168.111.152:8080/getactivexpubwatches response=$(getactivexpubwatches) + watchtxid) + # POST http://192.168.111.152:8080/watchtxid + # BODY {"txid":"b081ca7724386f549cf0c16f71db6affeb52ff7a0d9b606fb2e5c43faffd3387","confirmedCallbackURL":"192.168.111.233:1111/callback1conf","xconfCallbackURL":"192.168.111.233:1111/callbackXconf","nbxconf":6} + + response=$(watchtxidrequest "${line}") response_to_client "${response}" ${?} break ;; @@ -145,6 +151,13 @@ main() response_to_client "${response}" ${?} break ;; + newblock) + # curl (GET) 192.168.111.152:8080/newblock/000000000000005c987120f3b6f995c95749977ef1a109c89aa74ce4bba97c1f + + response=$(newblock "${line}") + response_to_client "${response}" ${?} + break + ;; getbestblockhash) # curl (GET) http://192.168.111.152:8080/getbestblockhash @@ -288,6 +301,14 @@ main() response_to_client "${response}" ${?} break ;; + ln_connectfund) + # POST http://192.168.111.152:8080/ln_connectfund + # BODY {"peer":"nodeId@ip:port","msatoshi":"100000","callbackUrl":"https://callbackUrl/?channelReady=f3y2c3cvm4uzg2gq"} + + response=$(ln_connectfund "${line}") + response_to_client "${response}" ${?} + break + ;; ln_getinvoice) # GET http://192.168.111.152:8080/ln_getinvoice/label # GET http://192.168.111.152:8080/ln_getinvoice/koNCcrSvhX3dmyFhW diff --git a/proxy_docker/app/script/watchrequest.sh b/proxy_docker/app/script/watchrequest.sh index 2d74727..549ff11 100644 --- a/proxy_docker/app/script/watchrequest.sh +++ b/proxy_docker/app/script/watchrequest.sh @@ -290,3 +290,47 @@ extend_watchers() { return ${returncode} } + +watchtxidrequest() { + trace "Entering watchtxidrequest()..." + + local returncode + local request=${1} + trace "[watchtxidrequest] request=${request}" + local txid=$(echo "${request}" | jq ".txid" | tr -d '"') + trace "[watchtxidrequest] txid=${txid}" + local cb1conf_url=$(echo "${request}" | jq ".confirmedCallbackURL" | tr -d '"') + trace "[watchtxidrequest] cb1conf_url=${cb1conf_url}" + local cbxconf_url=$(echo "${request}" | jq ".xconfCallbackURL" | tr -d '"') + trace "[watchtxidrequest] cbxconf_url=${cbxconf_url}" + local nbxconf=$(echo "${request}" | jq ".nbxconf") + trace "[watchtxidrequest] nbxconf=${nbxconf}" + local inserted + local id_inserted + local result + trace "[watchtxidrequest] Watch request on txid (${txid}), cb 1-conf (${cb1conf_url}) and cb x-conf (${cbxconf_url}) on ${nbxconf} confirmations." + + sql "INSERT OR IGNORE INTO watching_by_txid (txid, watching, callback1conf, callbackxconf, nbxconf) VALUES (\"${txid}\", 1, \"${cb1conf_url}\", \"${cbxconf_url}\", ${nbxconf})" + returncode=$? + trace_rc ${returncode} + if [ "${returncode}" -eq 0 ]; then + inserted=1 + id_inserted=$(sql "SELECT id FROM watching_by_txid WHERE txid='${txid}'") + trace "[watchtxidrequest] id_inserted: ${id_inserted}" + else + inserted=0 + fi + + local data="{\"id\":\"${id_inserted}\", + \"event\":\"watchtxid\", + \"inserted\":\"${inserted}\", + \"txid\":\"${txid}\", + \"confirmedCallbackURL\":\"${cb1conf_url}\", + \"xconfCallbackURL\":\"${cbxconf_url}\", + \"nbxconf\":${nbxconf}}" + trace "[watchtxidrequest] responding=${data}" + + echo "${data}" + + return ${returncode} +}