From 1140c9aa92d5a620523adb88febff35ab5a7d1df Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Sep 2019 08:55:17 -0400 Subject: [PATCH 01/42] Bump mixin-deep from 1.3.1 to 1.3.2 in /cyphernodeconf_docker (#132) Bumps [mixin-deep](https://github.com/jonschlinkert/mixin-deep) from 1.3.1 to 1.3.2. - [Release notes](https://github.com/jonschlinkert/mixin-deep/releases) - [Commits](https://github.com/jonschlinkert/mixin-deep/compare/1.3.1...1.3.2) Signed-off-by: dependabot[bot] --- cyphernodeconf_docker/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cyphernodeconf_docker/package-lock.json b/cyphernodeconf_docker/package-lock.json index 42a79a4..c68e746 100644 --- a/cyphernodeconf_docker/package-lock.json +++ b/cyphernodeconf_docker/package-lock.json @@ -3721,9 +3721,9 @@ "dev": true }, "mixin-deep": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", - "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", "dev": true, "requires": { "for-in": "^1.0.2", From fa73077e9c476719556082fd5a992e5422efe6ad Mon Sep 17 00:00:00 2001 From: Mayank Chhabra Date: Sat, 5 Oct 2019 16:48:50 +0530 Subject: [PATCH 02/42] API doc Links --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index adeda9c..8e87811 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,9 @@ The core component of cyphernode is a request handler which exposes HTTP endpoin ## Documentation -* Read the API docs here: https://github.com/SatoshiPortal/cyphernode/blob/master/doc/API.md +* Read the API docs here: + * API v0 (Current): https://github.com/SatoshiPortal/cyphernode/blob/master/doc/API.v0.md + * API v1 (RESTful): https://github.com/SatoshiPortal/cyphernode/blob/master/doc/API.v1.md * Installation documentation: https://github.com/SatoshiPortal/cyphernode/blob/master/doc/INSTALL.md * Step-by-step manual install (deprecated): https://github.com/SatoshiPortal/cyphernode/blob/master/doc/INSTALL-MANUAL-STEPS.md From 5cb63e3522c1522f922b5f61adea65796cb9cf80 Mon Sep 17 00:00:00 2001 From: Mayank Date: Fri, 11 Oct 2019 18:53:57 +0530 Subject: [PATCH 03/42] fix ln expose question's help text --- cyphernodeconf_docker/help.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cyphernodeconf_docker/help.json b/cyphernodeconf_docker/help.json index c321031..fe9a314 100644 --- a/cyphernodeconf_docker/help.json +++ b/cyphernodeconf_docker/help.json @@ -35,7 +35,7 @@ "lightning_nodecolor": "LN nodes have colors. Choose the color you want for yours in RGB format (RRGGBB). For example, pure red would be ff0000.", "lightning_datapath": "Path name to where LN's data files are stored. This directory will be mounted into the LN node's container. If running on OSX, check mountable directories in Docker's File Sharing configs.", "lightning_datapath_custom": " ", - "lightning_expose": "By default, LN node port will be published outside of Docker. Do you want to hide it so that your node can't be accessed from outside of the Docker network?", + "lightning_expose": "By default, LN node port will be published outside of Docker. Do you want to expose it so that your node can be accessed from outside of the Docker network?", "otsclient_datapath": "Full path where the OTS files will be stored. This path will be mounted into the otsclient container which will create the OTS files when stamping and update them when upgrading stamps. It will also be mounted to the proxy container so that it can serve the ots_getfile and send the OTS files to clients. If running on OSX, check mountable directories in Docker's File Sharing configs.", "otsclient_datapath_custom": " ", "installer_mode": "Only one installation mode is supported, right now: local docker (self-hosted). Choose wisely ;-)", From 82498ee25118797964ef967b411a1d7f42f4de5b Mon Sep 17 00:00:00 2001 From: SKP Date: Thu, 17 Oct 2019 17:58:37 +0200 Subject: [PATCH 04/42] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 8e87811..ff1d4a5 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,7 @@ Currently used to make sure callbacks have been called for missed transactions. * Created and maintained by www.satoshiportal.com * Dedicated full-time developer @kexkey * Project manager @FrancisPouliot +* Contributor: @\_\_escapee\_\_ * Disclaimer: as of release on Sept. 23 2018 the project is still it its early stages (Alpha) and many of the features have yet to be implemented. The core architecture and basic wallet operations and blockchain query functions are fully functional. # How to use cyphernode? From 147517c07ae964aa27c1ec009dd73d133aa301a8 Mon Sep 17 00:00:00 2001 From: G Date: Fri, 18 Oct 2019 08:53:12 -0400 Subject: [PATCH 05/42] Update api.properties add ```action_getblockhash=stats``` to api.properties template so it is generated for a new install. --- cyphernodeconf_docker/templates/gatekeeper/api.properties | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cyphernodeconf_docker/templates/gatekeeper/api.properties b/cyphernodeconf_docker/templates/gatekeeper/api.properties index f2f4739..2987271 100644 --- a/cyphernodeconf_docker/templates/gatekeeper/api.properties +++ b/cyphernodeconf_docker/templates/gatekeeper/api.properties @@ -8,6 +8,7 @@ action_helloworld=stats action_getblockchaininfo=stats action_installation_info=stats action_getmempoolinfo=stats +action_getblockhash=stats # Watcher can: action_watch=watcher @@ -57,4 +58,4 @@ action_ln_connectfund=spender action_conf=internal action_newblock=internal action_executecallbacks=internal -action_ots_backoffice=internal \ No newline at end of file +action_ots_backoffice=internal From f61aa4b734b82b77ac4b54ade6d48534fc66bf78 Mon Sep 17 00:00:00 2001 From: kexkey Date: Sat, 2 Nov 2019 21:03:35 -0400 Subject: [PATCH 06/42] rawtx was not detailed when sending tx --- proxy_docker/app/script/walletoperations.sh | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/proxy_docker/app/script/walletoperations.sh b/proxy_docker/app/script/walletoperations.sh index e5051d6..867d86c 100644 --- a/proxy_docker/app/script/walletoperations.sh +++ b/proxy_docker/app/script/walletoperations.sh @@ -39,10 +39,12 @@ spend() { local tx_replaceable=$(echo "${tx_details}" | jq '.result."bip125-replaceable"') tx_replaceable=$([ ${tx_replaceable} = "yes" ] && echo 1 || echo 0) local fees=$(echo "${tx_details}" | jq '.result.fee | fabs' | awk '{ printf "%.8f", $0 }') - local rawtx=$(echo "${tx_details}" | jq '.result.hex') + # 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}" > rawtx-${txid}.blob # Let's insert the txid in our little DB -- then we'll already have it when receiving confirmation - sql "INSERT OR IGNORE INTO tx (txid, hash, confirmations, timereceived, fee, size, vsize, is_replaceable, raw_tx) VALUES (\"${txid}\", ${tx_hash}, 0, ${tx_ts_firstseen}, ${fees}, ${tx_size}, ${tx_vsize}, ${tx_replaceable}, ${rawtx})" + sql "INSERT OR IGNORE INTO tx (txid, hash, confirmations, timereceived, fee, size, vsize, is_replaceable, raw_tx) VALUES (\"${txid}\", ${tx_hash}, 0, ${tx_ts_firstseen}, ${fees}, ${tx_size}, ${tx_vsize}, ${tx_replaceable}, readfile('rawtx-${txid}.blob'))" trace_rc $? id_inserted=$(sql "SELECT id FROM tx WHERE txid=\"${txid}\"") trace_rc $? @@ -51,6 +53,9 @@ spend() { data="{\"status\":\"accepted\"" data="${data},\"hash\":\"${txid}\"}" + + # Delete the temp file containing the raw tx (see above) + rm rawtx-${txid}.blob else local message=$(echo "${response}" | jq -e ".error.message") data="{\"message\":${message}}" @@ -289,10 +294,12 @@ batchspend() { local tx_replaceable=$(echo "${tx_details}" | jq '.result."bip125-replaceable"') tx_replaceable=$([ ${tx_replaceable} = "yes" ] && echo 1 || echo 0) local fees=$(echo "${tx_details}" | jq '.result.fee | fabs' | awk '{ printf "%.8f", $0 }') - local rawtx=$(echo "${tx_details}" | jq '.result.hex') + # 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}" > rawtx-${txid}.blob # Let's insert the txid in our little DB -- then we'll already have it when receiving confirmation - sql "INSERT OR IGNORE INTO tx (txid, hash, confirmations, timereceived, fee, size, vsize, is_replaceable, raw_tx) VALUES (\"${txid}\", ${tx_hash}, 0, ${tx_ts_firstseen}, ${fees}, ${tx_size}, ${tx_vsize}, ${tx_replaceable}, ${rawtx})" + sql "INSERT OR IGNORE INTO tx (txid, hash, confirmations, timereceived, fee, size, vsize, is_replaceable, raw_tx) VALUES (\"${txid}\", ${tx_hash}, 0, ${tx_ts_firstseen}, ${fees}, ${tx_size}, ${tx_vsize}, ${tx_replaceable}, readfile('rawtx-${txid}.blob'))" returncode=$? trace_rc ${returncode} if [ "${returncode}" -eq 0 ]; then @@ -304,6 +311,9 @@ batchspend() { data="{\"status\":\"accepted\"" data="${data},\"hash\":\"${txid}\"}" + + # Delete the temp file containing the raw tx (see above) + rm rawtx-${txid}.blob else local message=$(echo "${response}" | jq -e ".error.message") data="{\"message\":${message}}" From 5dea791af00d64b6c80ff88d80d9f90f0155e921 Mon Sep 17 00:00:00 2001 From: kexkey Date: Sat, 9 Nov 2019 18:18:53 -0500 Subject: [PATCH 07/42] It is actually compatible and supported to switch network --- dist/setup.sh | 6 ------ 1 file changed, 6 deletions(-) diff --git a/dist/setup.sh b/dist/setup.sh index dee2b03..0d58593 100755 --- a/dist/setup.sh +++ b/dist/setup.sh @@ -437,12 +437,6 @@ install_docker() { esac done fi - elif [[ $cmpStatus == 'incompatible' ]]; then - copy_file $cyphernodeconf_filepath/bitcoin/bitcoin.conf $BITCOIN_DATAPATH/bitcoin.conf.cyphernode 0 $SUDO_REQUIRED - copy_file $cyphernodeconf_filepath/bitcoin/bitcoin-client.conf $BITCOIN_DATAPATH/bitcoin-client.conf.cyphernode 0 $SUDO_REQUIRED - echo " Blockchain data is not compatible, due to misconfigured nets." - echo " Your cyphernode installation is most likely broken." - echo " Please check bitcoin.conf.cyphernode on how to repair it manually." else if [[ $cmpStatus == 'reindex' ]]; then echo " Warning Reindexing will take some time." From d43c4a71ba2ae73c94be35503d5e75e9b302cc2a Mon Sep 17 00:00:00 2001 From: cryptoskillz Date: Thu, 21 Nov 2019 17:16:49 +0700 Subject: [PATCH 08/42] Added clearly docker log instructions if cyphernode fails to start --- cyphernodeconf_docker/templates/installer/testdeployment.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cyphernodeconf_docker/templates/installer/testdeployment.sh b/cyphernodeconf_docker/templates/installer/testdeployment.sh index 94dde14..d08ea2e 100644 --- a/cyphernodeconf_docker/templates/installer/testdeployment.sh +++ b/cyphernodeconf_docker/templates/installer/testdeployment.sh @@ -78,7 +78,7 @@ EXIT_STATUS=$(($? | ${EXIT_STATUS})) printf "\r\n\e[1;32mTests finished.\e[0m\n" if [ "$EXIT_STATUS" -ne "0" ]; then - printf "\r\n\033[1;31mThere was an error during cyphernode installation. Please see Docker's logs for more information. Run ./testdeployment.sh to rerun the tests. Run ./stop.sh to stop cyphernode.\r\n\r\n\033[0m" + printf "\r\n\033[1;31mThere was an error during cyphernode installation. full logs: docker ps -q | xargs -L 1 docker logs , Containers logs: docker logs , list containers: docker ps .Please see Docker's logs for more information. Run ./testdeployment.sh to rerun the tests. Run ./stop.sh to stop cyphernode.\r\n\r\n\033[0m" exit 1 fi From 0ac083ab4ef8272986ce54ed05d0f0e1b5ab44ea Mon Sep 17 00:00:00 2001 From: g-homebase Date: Thu, 28 Nov 2019 11:11:54 -0500 Subject: [PATCH 09/42] FixeS --- proxy_docker/app/script/walletoperations.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/proxy_docker/app/script/walletoperations.sh b/proxy_docker/app/script/walletoperations.sh index 867d86c..295fbe6 100644 --- a/proxy_docker/app/script/walletoperations.sh +++ b/proxy_docker/app/script/walletoperations.sh @@ -164,15 +164,15 @@ getbalancebyxpub() { local returncode # addresses=$(./bitcoin-cli -rpcwallet=xpubwatching01.dat getaddressesbylabel upub5GtUcgGed1aGH4HKQ3vMYrsmLXwmHhS1AeX33ZvDgZiyvkGhNTvGd2TA5Lr4v239Fzjj4ZY48t6wTtXUy2yRgapf37QHgt6KWEZ6bgsCLpb | jq "keys" | tr -d '\n ') - data="{\"method\":\"getaddressesbylabel\",\"params\":[${xpub}]}" + data="{\"method\":\"getaddressesbylabel\",\"params\":[\"${xpub}\"]}" trace "[getbalancebyxpub] data=${data}" - addresses=$(send_to_xpub_watcher_wallet ${data} | jq "keys" | tr -d '\n ') + addresses=$(send_to_xpub_watcher_wallet ${data} | jq ".result | keys" | tr -d '\n ') # ./bitcoin-cli -rpcwallet=xpubwatching01.dat listunspent 0 9999999 "$addresses" | jq "[.[].amount] | add" - data="{\"method\":\"listunspent\",\"params\":[0, 9999999, \"${addresses}\"]}" + data="{\"method\":\"listunspent\",\"params\":[0,9999999,\"${addresses}\"]}" trace "[getbalancebyxpub] data=${data}" - balance=$(send_to_xpub_watcher_wallet ${data} | jq "[.[].amount] | add | . * 100000000 | trunc | . / 100000000") + balance=$(send_to_xpub_watcher_wallet ${data} | jq "[.result[].amount] | add | . * 100000000 | trunc | . / 100000000") returncode=$? trace_rc ${returncode} trace "[getbalancebyxpub] balance=${balance}" From 49299725e01b4273d2cbc12c96d0f21166e7b144 Mon Sep 17 00:00:00 2001 From: g-homebase Date: Thu, 28 Nov 2019 14:19:50 -0500 Subject: [PATCH 10/42] Fix watching errors --- proxy_docker/app/script/walletoperations.sh | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/proxy_docker/app/script/walletoperations.sh b/proxy_docker/app/script/walletoperations.sh index 295fbe6..cdc54ab 100644 --- a/proxy_docker/app/script/walletoperations.sh +++ b/proxy_docker/app/script/walletoperations.sh @@ -167,17 +167,15 @@ getbalancebyxpub() { data="{\"method\":\"getaddressesbylabel\",\"params\":[\"${xpub}\"]}" trace "[getbalancebyxpub] data=${data}" addresses=$(send_to_xpub_watcher_wallet ${data} | jq ".result | keys" | tr -d '\n ') - # ./bitcoin-cli -rpcwallet=xpubwatching01.dat listunspent 0 9999999 "$addresses" | jq "[.[].amount] | add" - - data="{\"method\":\"listunspent\",\"params\":[0,9999999,\"${addresses}\"]}" + data="{\"method\":\"listunspent\",\"params\":[0,9999999,${addresses}]}" trace "[getbalancebyxpub] data=${data}" - balance=$(send_to_xpub_watcher_wallet ${data} | jq "[.result[].amount] | add | . * 100000000 | trunc | . / 100000000") + balance=$(send_to_xpub_watcher_wallet ${data} | jq "[.result[].amount // 0 ] | add | . * 100000000 | trunc | . / 100000000") returncode=$? trace_rc ${returncode} trace "[getbalancebyxpub] balance=${balance}" - data="{\"event\":\"${event}\",\"xpub\":\"${xpub}\",\"balance\":${balance}}" + data="{\"event\":\"${event}\",\"xpub\":\"${xpub}\",\"balance\":${balance:-0}}" echo "${data}" From ed40433b28c6ff7659f6461058d75fa92b9783c7 Mon Sep 17 00:00:00 2001 From: kexkey Date: Thu, 28 Nov 2019 11:41:33 -0500 Subject: [PATCH 11/42] Provide a msg to watch that will be published to a topic when confirmation --- .../installer/docker/docker-compose.yaml | 1 + doc/API.v0.md | 8 ++- doc/openapi/v0/cyphernode-api.yaml | 6 ++ proxy_docker/app/data/cyphernode.sql | 1 + .../data/sqlmigrate20191127_0.2.4-0.3.0.sh | 14 ++++ .../data/sqlmigrate20191127_0.2.4-0.3.0.sql | 1 + proxy_docker/app/script/callbacks_job.sh | 51 ++++++++------ proxy_docker/app/script/confirmation.sh | 66 ++++++++++++------- proxy_docker/app/script/requesthandler.sh | 1 + proxy_docker/app/script/watchrequest.sh | 8 ++- 10 files changed, 108 insertions(+), 49 deletions(-) create mode 100644 proxy_docker/app/data/sqlmigrate20191127_0.2.4-0.3.0.sh create mode 100644 proxy_docker/app/data/sqlmigrate20191127_0.2.4-0.3.0.sql diff --git a/cyphernodeconf_docker/templates/installer/docker/docker-compose.yaml b/cyphernodeconf_docker/templates/installer/docker/docker-compose.yaml index 7971a87..255a974 100644 --- a/cyphernodeconf_docker/templates/installer/docker/docker-compose.yaml +++ b/cyphernodeconf_docker/templates/installer/docker/docker-compose.yaml @@ -106,6 +106,7 @@ services: image: eclipse-mosquitto:1.6 networks: - cyphernodenet + - cyphernodeappsnet restart: always # deploy: # placement: diff --git a/doc/API.v0.md b/doc/API.v0.md index d62a1a2..ae1c57e 100644 --- a/doc/API.v0.md +++ b/doc/API.v0.md @@ -9,7 +9,7 @@ Inserts the address and callbacks in the DB and imports the address to the Watch ```http POST http://cyphernode:8888/watch with body... -{"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","unconfirmedCallbackURL":"192.168.111.233:1111/callback0conf","confirmedCallbackURL":"192.168.111.233:1111/callback1conf"} +{"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","unconfirmedCallbackURL":"192.168.111.233:1111/callback0conf","confirmedCallbackURL":"192.168.111.233:1111/callback1conf","eventMessage":"{\"bounce_address}\":\"tb1q6s0ppwk2msdewal3mu90ahfhpyepawnw6wdk8t\",\"nb_conf\":6}"} ``` Proxy response: @@ -26,7 +26,8 @@ Proxy response: "estimatesmartfee2blocks": "0.000010", "estimatesmartfee6blocks": "0.000010", "estimatesmartfee36blocks": "0.000010", - "estimatesmartfee144blocks": "0.000010" + "estimatesmartfee144blocks": "0.000010", + "eventMessage": "{\"bounce_address}\":\"tb1q6s0ppwk2msdewal3mu90ahfhpyepawnw6wdk8t\",\"nb_conf\":6}" } ``` @@ -66,7 +67,8 @@ Proxy response: "imported":"1", "unconfirmedCallbackURL":"192.168.133.233:1111/callback0conf", "confirmedCallbackURL":"192.168.133.233:1111/callback1conf", - "watching_since":"2018-09-06 21:14:03"} + "watching_since":"2018-09-06 21:14:03", + "eventMessage":"{\"bounce_address}\":\"tb1q6s0ppwk2msdewal3mu90ahfhpyepawnw6wdk8t\",\"nb_conf\":6}"} ] } ``` diff --git a/doc/openapi/v0/cyphernode-api.yaml b/doc/openapi/v0/cyphernode-api.yaml index c71a4fb..5d8b5a9 100644 --- a/doc/openapi/v0/cyphernode-api.yaml +++ b/doc/openapi/v0/cyphernode-api.yaml @@ -52,6 +52,7 @@ paths: - "address" - "confirmedCallbackURL" - "unconfirmedCallbackURL" + - "event_message" properties: address: $ref: '#/components/schemas/TypeAddressString' @@ -61,6 +62,8 @@ paths: confirmedCallbackURL: type: "string" format: "url" + event_message: + type: "string" responses: '200': description: "successfully created" @@ -1543,6 +1546,7 @@ components: - "address" - "unconfirmedCallbackURL" - "confirmedCallbackURL" + - "event_message" properties: id: type: "string" @@ -1574,6 +1578,8 @@ components: type: "string" watching_since: type: "string" + event_message: + type: "string" WatchedByXpubAddress: type: "object" required: diff --git a/proxy_docker/app/data/cyphernode.sql b/proxy_docker/app/data/cyphernode.sql index 97aeef0..743ce18 100644 --- a/proxy_docker/app/data/cyphernode.sql +++ b/proxy_docker/app/data/cyphernode.sql @@ -23,6 +23,7 @@ CREATE TABLE watching ( imported INTEGER DEFAULT FALSE, watching_by_pub32_id INTEGER REFERENCES watching_by_pub32, pub32_index INTEGER, + event_message TEXT, inserted_ts INTEGER DEFAULT CURRENT_TIMESTAMP ); diff --git a/proxy_docker/app/data/sqlmigrate20191127_0.2.4-0.3.0.sh b/proxy_docker/app/data/sqlmigrate20191127_0.2.4-0.3.0.sh new file mode 100644 index 0000000..83e21bf --- /dev/null +++ b/proxy_docker/app/data/sqlmigrate20191127_0.2.4-0.3.0.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +echo "Checking for watching event support in DB..." +count=$(sqlite3 $DB_FILE "select count(*) from pragma_table_info('watching') where name='event_message'") +if [ "${count}" -eq "0" ]; then + # event_message not there, we have to migrate + echo "Migrating database for event triggered on watch notif..." + echo "Backing up current DB..." + cp $DB_FILE $DB_FILE-sqlmigrate20191127_0.2.4-0.3.0 + echo "Altering DB..." + cat sqlmigrate20191127_0.2.4-0.3.0.sql | sqlite3 $DB_FILE +else + echo "Database watching event support migration already done, skipping!" +fi diff --git a/proxy_docker/app/data/sqlmigrate20191127_0.2.4-0.3.0.sql b/proxy_docker/app/data/sqlmigrate20191127_0.2.4-0.3.0.sql new file mode 100644 index 0000000..2a95bee --- /dev/null +++ b/proxy_docker/app/data/sqlmigrate20191127_0.2.4-0.3.0.sql @@ -0,0 +1 @@ +ALTER TABLE watching ADD COLUMN event_message TEXT; diff --git a/proxy_docker/app/script/callbacks_job.sh b/proxy_docker/app/script/callbacks_job.sh index 294880a..20381b9 100644 --- a/proxy_docker/app/script/callbacks_job.sh +++ b/proxy_docker/app/script/callbacks_job.sh @@ -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 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') + 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, event_message 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 @@ -30,7 +30,7 @@ do_callbacks() { fi done - 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') + 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, event_message 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} @@ -61,6 +61,17 @@ ln_manage_callback() { local row=$@ trace "[ln_manage_callback] row=${row}" + + local callback_url=$(echo "${row}" | cut -d '|' -f4) + trace "[ln_manage_callback] callback_url=${callback_url}" + + if [ -z "${callback_url}" ]; then + # No callback url provided for that invoice + trace "[ln_manage_callback] No callback url provided for that invoice" + sql "UPDATE ln_invoice SET calledback=1 WHERE id=\"${id}\"" + trace_rc $? + return + fi local id=$(echo "${row}" | cut -d '|' -f1) trace "[ln_manage_callback] id=${id}" @@ -68,8 +79,6 @@ ln_manage_callback() { trace "[ln_manage_callback] label=${label}" local bolt11=$(echo "${row}" | cut -d '|' -f3) trace "[ln_manage_callback] bolt11=${bolt11}" - local callback_url=$(echo "${row}" | cut -d '|' -f4) - trace "[ln_manage_callback] callback_url=${callback_url}" local payment_hash=$(echo "${row}" | cut -d '|' -f5) trace "[ln_manage_callback] payment_hash=${payment_hash}" local msatoshi=$(echo "${row}" | cut -d '|' -f6) @@ -88,13 +97,6 @@ ln_manage_callback() { trace "[ln_manage_callback] expires_at=${expires_at}" local returncode - if [ -z "${callback_url}" ]; then - # No callback url provided for that invoice - sql "UPDATE ln_invoice SET calledback=1 WHERE id=\"${id}\"" - trace_rc $? - return - fi - data="{\"id\":\"${id}\"," data="${data}\"label\":\"${label}\"," data="${data}\"bolt11\":\"${bolt11}\"," @@ -149,13 +151,22 @@ build_callback() { local label local derivation_path - # callback0conf, address, txid, vout, amount, confirmations, timereceived, fee, size, vsize, blockhash, blockheight, blocktime, w.id + local event_message + + # w.callback0conf, address, txid, vout, amount, confirmations, timereceived, fee, size, vsize, blockhash, blockheight, blocktime, + # w.id, is_replaceable, pub32_index, pub32, label, derivation_path, event_message + + url=$(echo "${row}" | cut -d '|' -f1) + trace "[build_callback] url=${url}" + if [ -z "${url}" ]; then + # No callback url provided for that watch + trace "[build_callback] No callback url provided for that watch, skipping webhook call" + return + fi trace "[build_callback] row=${row}" id=$(echo "${row}" | cut -d '|' -f14) trace "[build_callback] id=${id}" - url=$(echo "${row}" | cut -d '|' -f1) - trace "[build_callback] url=${url}" address=$(echo "${row}" | cut -d '|' -f2) trace "[build_callback] address=${address}" txid=$(echo "${row}" | cut -d '|' -f3) @@ -199,6 +210,8 @@ build_callback() { derivation_path=$(echo "${row}" | cut -d '|' -f19) trace "[build_callback] derivation_path=${derivation_path}" fi + event_message=$(echo "${row}" | cut -d '|' -f20) + trace "[build_callback] event_message=${event_message}" data="{\"id\":\"${id}\"," data="${data}\"address\":\"${address}\"," @@ -212,19 +225,19 @@ build_callback() { if [ -n "${fee}" ]; then data="${data}\"fees\":${fee}," fi - data="${data}\"is_replaceable\":${is_replaceable}" + data="${data}\"is_replaceable\":${is_replaceable}," if [ -n "${blocktime}" ]; then - data="${data},\"blockhash\":\"${blockhash}\"," + data="${data}\"blockhash\":\"${blockhash}\"," data="${data}\"blocktime\":\"$(date -Is -d @${blocktime})\"," - data="${data}\"blockheight\":${blockheight}" + 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}\"" + data="${data}\"pub32_derivation_path\":\"${derivation_path}\"," fi - data="${data}}" + data="${data}\"event_message\":\"${event_message}\"}" trace "[build_callback] data=${data}" curl_callback "${url}" "${data}" diff --git a/proxy_docker/app/script/confirmation.sh b/proxy_docker/app/script/confirmation.sh index 48bbfa1..c3aa459 100644 --- a/proxy_docker/app/script/confirmation.sh +++ b/proxy_docker/app/script/confirmation.sh @@ -58,7 +58,7 @@ confirmation() { notfirst=true fi done - local rows=$(sql "SELECT id, address, watching_by_pub32_id, pub32_index FROM watching WHERE address IN (${addresseswhere}) AND watching") + local rows=$(sql "SELECT id, address, watching_by_pub32_id, pub32_index, event_message FROM watching WHERE address IN (${addresseswhere}) AND watching") if [ ${#rows} -eq 0 ]; then trace "[confirmation] No watched address in this tx!" return 0 @@ -137,45 +137,63 @@ confirmation() { rm rawtx-${txid}.blob ######################################################################################################## - # Let's now insert in the join table if not already done + + local event_message + + # Let's see if we need to insert tx in the join table 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 it" - local watching_id + for row in ${rows} + do - # If the tx is batched and pays multiple watched addresses, we have to insert - # those additional addresses in watching_tx! - for row in ${rows} - do + address=$(echo "${row}" | cut -d '|' -f2) + ######################################################################################################## + # Let's now insert in the join table if not already done + if [ -z "${tx}" ]; then + 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 + # those additional addresses in watching_tx! watching_id=$(echo "${row}" | cut -d '|' -f1) - address=$(echo "${row}" | cut -d '|' -f2) # In the case of us spending to a watched address, the address appears twice in the details, # once on the spend side (negative amount) and once on the receiving side (positive amount) tx_vout_n=$(echo "${tx_details}" | jq ".result.details | map(select(.address==\"${address}\"))[0] | .vout") tx_vout_amount=$(echo "${tx_details}" | jq ".result.details | map(select(.address==\"${address}\"))[0] | .amount | fabs" | awk '{ printf "%.8f", $0 }') sql "INSERT OR IGNORE INTO watching_tx (watching_id, tx_id, vout, amount) VALUES (${watching_id}, ${id_inserted}, ${tx_vout_n}, ${tx_vout_amount})" trace_rc $? - done - else - trace "[confirmation] For this tx, there's already watching_tx rows" - fi - ######################################################################################################## + else + trace "[confirmation] For this tx, there's already watching_tx rows" + fi + ######################################################################################################## - ######################################################################################################## - # Let's now grow the watch window in the case of a xpub watcher... - trace "[confirmation] Let's now grow the watch window in the case of a xpub watcher" - - for row in ${rows} - do + ######################################################################################################## + # Let's now grow the watch window in the case of a xpub watcher... watching_by_pub32_id=$(echo "${row}" | cut -d '|' -f3) - pub32_index=$(echo "${row}" | cut -d '|' -f4) if [ -n "${watching_by_pub32_id}" ]; then + trace "[confirmation] Let's now grow the watch window in the case of a xpub watcher" + + pub32_index=$(echo "${row}" | cut -d '|' -f4) extend_watchers ${watching_by_pub32_id} ${pub32_index} fi - done + ######################################################################################################## - ######################################################################################################## + ######################################################################################################## + # Let's publish the event if needed + event_message=$(echo "${row}" | cut -d '|' -f5) + if [ -n "${event_message}" ]; then + # There's an event message, let's publish it! + + # We use the pid as the response-topic, so there's no conflict in responses. + trace "[confirmation] mosquitto_pub -h broker -t conf_event -m \"{\"address\":\"${address}\",\"confirmations\":${tx_nb_conf},\"event_message\":\"${event_message}\"}\"" + response=$(mosquitto_pub -h broker -t conf_event -m "{\"address\":\"${address}\",\"confirmations\":${tx_nb_conf},\"event_message\":\"${event_message}\"}") + returncode=$? + trace_rc ${returncode} + + fi + ######################################################################################################## + + done ) 201>./.confirmation.lock diff --git a/proxy_docker/app/script/requesthandler.sh b/proxy_docker/app/script/requesthandler.sh index dad4ca2..c908a9a 100644 --- a/proxy_docker/app/script/requesthandler.sh +++ b/proxy_docker/app/script/requesthandler.sh @@ -89,6 +89,7 @@ main() { 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","confirmedCallbackURL":"192.168.111.233:1111/callback1conf","eventMessage":"{\"bounce_address}\":\"tb1q6s0ppwk2msdewal3mu90ahfhpyepawnw6wdk8t\",\"nb_conf\":6}"} response=$(watchrequest "${line}") response_to_client "${response}" ${?} diff --git a/proxy_docker/app/script/watchrequest.sh b/proxy_docker/app/script/watchrequest.sh index a893e82..f5c8374 100644 --- a/proxy_docker/app/script/watchrequest.sh +++ b/proxy_docker/app/script/watchrequest.sh @@ -14,11 +14,12 @@ watchrequest() { local address=$(echo "${request}" | jq -r ".address") local cb0conf_url=$(echo "${request}" | jq -r ".unconfirmedCallbackURL") local cb1conf_url=$(echo "${request}" | jq -r ".confirmedCallbackURL") + local event_message=$(echo "${request}" | jq -r ".event_message") local imported local inserted local id_inserted local result - trace "[watchrequest] Watch request on address (${address}), cb 0-conf (${cb0conf_url}), cb 1-conf (${cb1conf_url})" + trace "[watchrequest] Watch request on address (${address}), cb 0-conf (${cb0conf_url}), cb 1-conf (${cb1conf_url}) with event_message=${event_message}" result=$(importaddress_rpc "${address}") returncode=$? @@ -29,7 +30,7 @@ watchrequest() { imported=0 fi - sql "INSERT OR REPLACE INTO watching (address, watching, callback0conf, callback1conf, imported) VALUES (\"${address}\", 1, \"${cb0conf_url}\", \"${cb1conf_url}\", ${imported})" + sql "INSERT OR REPLACE INTO watching (address, watching, callback0conf, callback1conf, imported, event_message) VALUES (\"${address}\", 1, \"${cb0conf_url}\", \"${cb1conf_url}\", ${imported}, '${event_message}')" returncode=$? trace_rc ${returncode} if [ "${returncode}" -eq 0 ]; then @@ -63,7 +64,8 @@ watchrequest() { \"estimatesmartfee2blocks\":\"${fees2blocks}\", \"estimatesmartfee6blocks\":\"${fees6blocks}\", \"estimatesmartfee36blocks\":\"${fees36blocks}\", - \"estimatesmartfee144blocks\":\"${fees144blocks}\"}" + \"estimatesmartfee144blocks\":\"${fees144blocks}\", + \"event_message\":\"${event_message}\"}" trace "[watchrequest] responding=${data}" echo "${data}" From 1ae5bf1dc0c48acd1d26c696cf5d305c5470c2f8 Mon Sep 17 00:00:00 2001 From: kexkey Date: Thu, 28 Nov 2019 15:56:12 -0500 Subject: [PATCH 12/42] Now expecting event_message in base64 and some fixes --- proxy_docker/app/script/callbacks_job.sh | 6 +++--- proxy_docker/app/script/confirmation.sh | 1 - proxy_docker/app/script/requesthandler.sh | 2 +- proxy_docker/app/script/watchrequest.sh | 26 +++++++++++++++++++---- 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/proxy_docker/app/script/callbacks_job.sh b/proxy_docker/app/script/callbacks_job.sh index 20381b9..27a2b30 100644 --- a/proxy_docker/app/script/callbacks_job.sh +++ b/proxy_docker/app/script/callbacks_job.sh @@ -61,7 +61,9 @@ ln_manage_callback() { local row=$@ trace "[ln_manage_callback] row=${row}" - + + local id=$(echo "${row}" | cut -d '|' -f1) + trace "[ln_manage_callback] id=${id}" local callback_url=$(echo "${row}" | cut -d '|' -f4) trace "[ln_manage_callback] callback_url=${callback_url}" @@ -73,8 +75,6 @@ ln_manage_callback() { return fi - local id=$(echo "${row}" | cut -d '|' -f1) - trace "[ln_manage_callback] id=${id}" local label=$(echo "${row}" | cut -d '|' -f2) trace "[ln_manage_callback] label=${label}" local bolt11=$(echo "${row}" | cut -d '|' -f3) diff --git a/proxy_docker/app/script/confirmation.sh b/proxy_docker/app/script/confirmation.sh index c3aa459..b079659 100644 --- a/proxy_docker/app/script/confirmation.sh +++ b/proxy_docker/app/script/confirmation.sh @@ -189,7 +189,6 @@ confirmation() { response=$(mosquitto_pub -h broker -t conf_event -m "{\"address\":\"${address}\",\"confirmations\":${tx_nb_conf},\"event_message\":\"${event_message}\"}") returncode=$? trace_rc ${returncode} - fi ######################################################################################################## diff --git a/proxy_docker/app/script/requesthandler.sh b/proxy_docker/app/script/requesthandler.sh index c908a9a..25c424e 100644 --- a/proxy_docker/app/script/requesthandler.sh +++ b/proxy_docker/app/script/requesthandler.sh @@ -89,7 +89,7 @@ main() { 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","confirmedCallbackURL":"192.168.111.233:1111/callback1conf","eventMessage":"{\"bounce_address}\":\"tb1q6s0ppwk2msdewal3mu90ahfhpyepawnw6wdk8t\",\"nb_conf\":6}"} + # BODY {"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","confirmedCallbackURL":"192.168.111.233:1111/callback1conf","eventMessage":"eyJib3VuY2VfYWRkcmVzcyI6InRiMXE2czBwcHdrMm1zZGV3YWwzbXU5MGFoZmhweWVwYXdudzZ3ZGs4dCIsIm5iX2NvbmYiOjZ9Cg=="} response=$(watchrequest "${line}") response_to_client "${response}" ${?} diff --git a/proxy_docker/app/script/watchrequest.sh b/proxy_docker/app/script/watchrequest.sh index f5c8374..a28809b 100644 --- a/proxy_docker/app/script/watchrequest.sh +++ b/proxy_docker/app/script/watchrequest.sh @@ -11,10 +11,28 @@ watchrequest() { local returncode local request=${1} - local address=$(echo "${request}" | jq -r ".address") - local cb0conf_url=$(echo "${request}" | jq -r ".unconfirmedCallbackURL") - local cb1conf_url=$(echo "${request}" | jq -r ".confirmedCallbackURL") - local event_message=$(echo "${request}" | jq -r ".event_message") + local address=$(echo "${request}" | jq -er ".address") + local cb0conf_url + cb0conf_url=$(echo "${request}" | jq -er ".unconfirmedCallbackURL") + if [ "$?" -ne "0" ]; then + # unconfirmedCallbackURL tag null, so there's no unconfirmedCallbackURL + trace "[watchrequest] unconfirmedCallbackURL=" + unconfirmedCallbackURL= + fi + local cb1conf_url + cb1conf_url=$(echo "${request}" | jq -er ".confirmedCallbackURL") + if [ "$?" -ne "0" ]; then + # confirmedCallbackURL tag null, so there's no confirmedCallbackURL + trace "[watchrequest] confirmedCallbackURL=" + confirmedCallbackURL= + fi + local event_message + event_message=$(echo "${request}" | jq -er ".eventMessage") + if [ "$?" -ne "0" ]; then + # event_message tag null, so there's no event_message + trace "[watchrequest] event_message=" + event_message= + fi local imported local inserted local id_inserted From 8e3ac5ab2c3cfbc9a4dc055e888a1076dc874a77 Mon Sep 17 00:00:00 2001 From: kexkey Date: Thu, 28 Nov 2019 20:56:30 -0500 Subject: [PATCH 13/42] Added some data to confirmation event --- proxy_docker/app/script/confirmation.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/proxy_docker/app/script/confirmation.sh b/proxy_docker/app/script/confirmation.sh index b079659..396b63d 100644 --- a/proxy_docker/app/script/confirmation.sh +++ b/proxy_docker/app/script/confirmation.sh @@ -149,6 +149,7 @@ confirmation() { address=$(echo "${row}" | cut -d '|' -f2) ######################################################################################################## # Let's now insert in the join table if not already done + tx_vout_amount=$(echo "${tx_details}" | jq ".result.details | map(select(.address==\"${address}\"))[0] | .amount | fabs" | awk '{ printf "%.8f", $0 }') if [ -z "${tx}" ]; then trace "[confirmation] For this tx, there's no watching_tx row, let's create it" local watching_id @@ -159,7 +160,6 @@ confirmation() { # In the case of us spending to a watched address, the address appears twice in the details, # once on the spend side (negative amount) and once on the receiving side (positive amount) tx_vout_n=$(echo "${tx_details}" | jq ".result.details | map(select(.address==\"${address}\"))[0] | .vout") - tx_vout_amount=$(echo "${tx_details}" | jq ".result.details | map(select(.address==\"${address}\"))[0] | .amount | fabs" | awk '{ printf "%.8f", $0 }') sql "INSERT OR IGNORE INTO watching_tx (watching_id, tx_id, vout, amount) VALUES (${watching_id}, ${id_inserted}, ${tx_vout_n}, ${tx_vout_amount})" trace_rc $? else @@ -185,8 +185,8 @@ confirmation() { # There's an event message, let's publish it! # We use the pid as the response-topic, so there's no conflict in responses. - trace "[confirmation] mosquitto_pub -h broker -t conf_event -m \"{\"address\":\"${address}\",\"confirmations\":${tx_nb_conf},\"event_message\":\"${event_message}\"}\"" - response=$(mosquitto_pub -h broker -t conf_event -m "{\"address\":\"${address}\",\"confirmations\":${tx_nb_conf},\"event_message\":\"${event_message}\"}") + trace "[confirmation] mosquitto_pub -h broker -t conf_event -m \"{\"txid\":\"${txid}\",\"address\":\"${address}\",\"amount\":${tx_vout_amount},\"confirmations\":${tx_nb_conf},\"event_message\":\"${event_message}\"}\"" + response=$(mosquitto_pub -h broker -t conf_event -m "{\"txid\":\"${txid}\",\"address\":\"${address}\",\"amount\":${tx_vout_amount},\"confirmations\":${tx_nb_conf},\"event_message\":\"${event_message}\"}") returncode=$? trace_rc ${returncode} fi From ebd797871d8bc88211703f253b574a15083bb5e1 Mon Sep 17 00:00:00 2001 From: kexkey Date: Mon, 2 Dec 2019 11:19:28 -0500 Subject: [PATCH 14/42] tx_confirmation and newblock events published --- proxy_docker/app/script/confirmation.sh | 5 ++--- proxy_docker/app/script/newblock.sh | 12 ++++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/proxy_docker/app/script/confirmation.sh b/proxy_docker/app/script/confirmation.sh index 396b63d..37b0599 100644 --- a/proxy_docker/app/script/confirmation.sh +++ b/proxy_docker/app/script/confirmation.sh @@ -184,9 +184,8 @@ confirmation() { if [ -n "${event_message}" ]; then # There's an event message, let's publish it! - # We use the pid as the response-topic, so there's no conflict in responses. - trace "[confirmation] mosquitto_pub -h broker -t conf_event -m \"{\"txid\":\"${txid}\",\"address\":\"${address}\",\"amount\":${tx_vout_amount},\"confirmations\":${tx_nb_conf},\"event_message\":\"${event_message}\"}\"" - response=$(mosquitto_pub -h broker -t conf_event -m "{\"txid\":\"${txid}\",\"address\":\"${address}\",\"amount\":${tx_vout_amount},\"confirmations\":${tx_nb_conf},\"event_message\":\"${event_message}\"}") + trace "[confirmation] mosquitto_pub -h broker -t tx_confirmation -m \"{\"txid\":\"${txid}\",\"address\":\"${address}\",\"amount\":${tx_vout_amount},\"confirmations\":${tx_nb_conf},\"event_message\":\"${event_message}\"}\"" + response=$(mosquitto_pub -h broker -t tx_confirmation -m "{\"txid\":\"${txid}\",\"address\":\"${address}\",\"amount\":${tx_vout_amount},\"confirmations\":${tx_nb_conf},\"event_message\":\"${event_message}\"}") returncode=$? trace_rc ${returncode} fi diff --git a/proxy_docker/app/script/newblock.sh b/proxy_docker/app/script/newblock.sh index c266eeb..1ae3d05 100644 --- a/proxy_docker/app/script/newblock.sh +++ b/proxy_docker/app/script/newblock.sh @@ -2,6 +2,7 @@ . ./trace.sh . ./callbacks_txid.sh +. ./blockchainrpc.sh newblock() { trace "Entering newblock()..." @@ -9,5 +10,16 @@ newblock() { local request=${1} local blockhash=$(echo "${request}" | cut -d ' ' -f2 | cut -d '/' -f3) + local blockinfo + blockinfo=$(get_block_info ${blockhash}) + + local blockheight + blockheight=$(echo ${blockinfo} | jq -r ".result.height") + + trace "[newblock] mosquitto_pub -h broker -t newblock -m \"{\"blockhash\":\"${blockhash}\",\"blockheight\":\"${blockheight}\"}\"" + response=$(mosquitto_pub -h broker -t newblock -m "{\"blockhash\":\"${blockhash}\",\"blockheight\":\"${blockheight}\"}") + returncode=$? + trace_rc ${returncode} + do_callbacks_txid } From 08d221274b55ed5d79b73947db62544ecb3ea7fb Mon Sep 17 00:00:00 2001 From: kexkey Date: Mon, 2 Dec 2019 11:26:20 -0500 Subject: [PATCH 15/42] We want the eventMessage to be base64 encoded to avoid escaping maze --- doc/API.v0.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/API.v0.md b/doc/API.v0.md index ae1c57e..93b371d 100644 --- a/doc/API.v0.md +++ b/doc/API.v0.md @@ -9,7 +9,7 @@ Inserts the address and callbacks in the DB and imports the address to the Watch ```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":"{\"bounce_address}\":\"tb1q6s0ppwk2msdewal3mu90ahfhpyepawnw6wdk8t\",\"nb_conf\":6}"} +{"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","unconfirmedCallbackURL":"192.168.111.233:1111/callback0conf","confirmedCallbackURL":"192.168.111.233:1111/callback1conf","eventMessage":"eyJib3VuY2VfYWRkcmVzcyI6InRiMXE2czBwcHdrMm1zZGV3YWwzbXU5MGFoZmhweWVwYXdudzZ3ZGs4dCIsIm5iX2NvbmYiOjZ9Cg=="} ``` Proxy response: @@ -27,7 +27,7 @@ Proxy response: "estimatesmartfee6blocks": "0.000010", "estimatesmartfee36blocks": "0.000010", "estimatesmartfee144blocks": "0.000010", - "eventMessage": "{\"bounce_address}\":\"tb1q6s0ppwk2msdewal3mu90ahfhpyepawnw6wdk8t\",\"nb_conf\":6}" + "eventMessage": "eyJib3VuY2VfYWRkcmVzcyI6InRiMXE2czBwcHdrMm1zZGV3YWwzbXU5MGFoZmhweWVwYXdudzZ3ZGs4dCIsIm5iX2NvbmYiOjZ9Cg==" } ``` @@ -68,7 +68,7 @@ Proxy response: "unconfirmedCallbackURL":"192.168.133.233:1111/callback0conf", "confirmedCallbackURL":"192.168.133.233:1111/callback1conf", "watching_since":"2018-09-06 21:14:03", - "eventMessage":"{\"bounce_address}\":\"tb1q6s0ppwk2msdewal3mu90ahfhpyepawnw6wdk8t\",\"nb_conf\":6}"} + "eventMessage":"eyJib3VuY2VfYWRkcmVzcyI6InRiMXE2czBwcHdrMm1zZGV3YWwzbXU5MGFoZmhweWVwYXdudzZ3ZGs4dCIsIm5iX2NvbmYiOjZ9Cg=="} ] } ``` From 8021864f93d330432ad877a6760eab9db1529d3e Mon Sep 17 00:00:00 2001 From: kexkey Date: Mon, 2 Dec 2019 13:19:41 -0500 Subject: [PATCH 16/42] Event published on spend --- proxy_docker/app/script/requesthandler.sh | 2 +- proxy_docker/app/script/walletoperations.sh | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/proxy_docker/app/script/requesthandler.sh b/proxy_docker/app/script/requesthandler.sh index 25c424e..0d5c430 100644 --- a/proxy_docker/app/script/requesthandler.sh +++ b/proxy_docker/app/script/requesthandler.sh @@ -257,7 +257,7 @@ main() { ;; spend) # POST http://192.168.111.152:8080/spend - # BODY {"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","amount":0.00233} + # BODY {"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","amount":0.00233,"eventMessage":"eyJib3VuY2VfYWRkcmVzcyI6InRiMXE2czBwcHdrMm1zZGV3YWwzbXU5MGFoZmhweWVwYXdudzZ3ZGs4dCIsIm5iX2NvbmYiOjZ9Cg=="} response=$(spend "${line}") response_to_client "${response}" ${?} diff --git a/proxy_docker/app/script/walletoperations.sh b/proxy_docker/app/script/walletoperations.sh index cdc54ab..f1ed68f 100644 --- a/proxy_docker/app/script/walletoperations.sh +++ b/proxy_docker/app/script/walletoperations.sh @@ -43,6 +43,24 @@ spend() { # it to a temp file for it to be read by sqlite3 and then delete the file echo "${tx_raw_details}" > rawtx-${txid}.blob + ######################################################################################################## + # Let's publish the event if needed + local event_message + event_message=$(echo "${request}" | jq -er ".eventMessage") + if [ "$?" -ne "0" ]; then + # event_message tag null, so there's no event_message + trace "[spend] event_message=" + event_message= + else + # There's an event message, let's publish it! + + trace "[spend] mosquitto_pub -h broker -t spend -m \"{\"txid\":\"${txid}\",\"address\":\"${address}\",\"amount\":${tx_amount},\"event_message\":\"${event_message}\"}\"" + response=$(mosquitto_pub -h broker -t spend -m "{\"txid\":\"${txid}\",\"address\":\"${address}\",\"amount\":${tx_amount},\"event_message\":\"${event_message}\"}") + returncode=$? + trace_rc ${returncode} + fi + ######################################################################################################## + # Let's insert the txid in our little DB -- then we'll already have it when receiving confirmation sql "INSERT OR IGNORE INTO tx (txid, hash, confirmations, timereceived, fee, size, vsize, is_replaceable, raw_tx) VALUES (\"${txid}\", ${tx_hash}, 0, ${tx_ts_firstseen}, ${fees}, ${tx_size}, ${tx_vsize}, ${tx_replaceable}, readfile('rawtx-${txid}.blob'))" trace_rc $? From 52403f9a0adff33fc2012fd64d8830b7e67df47a Mon Sep 17 00:00:00 2001 From: kexkey Date: Mon, 2 Dec 2019 15:54:40 -0500 Subject: [PATCH 17/42] vout_n added in tx_confirmation event payload --- proxy_docker/app/script/confirmation.sh | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/proxy_docker/app/script/confirmation.sh b/proxy_docker/app/script/confirmation.sh index 37b0599..17b54b4 100644 --- a/proxy_docker/app/script/confirmation.sh +++ b/proxy_docker/app/script/confirmation.sh @@ -139,6 +139,7 @@ confirmation() { ######################################################################################################## local event_message + local watching_id # Let's see if we need to insert tx in the join table tx=$(sql "SELECT tx_id FROM watching_tx WHERE tx_id=\"${tx}\"") @@ -147,19 +148,19 @@ confirmation() { do address=$(echo "${row}" | cut -d '|' -f2) + tx_vout_amount=$(echo "${tx_details}" | jq ".result.details | map(select(.address==\"${address}\"))[0] | .amount | fabs" | awk '{ printf "%.8f", $0 }') + # In the case of us spending to a watched address, the address appears twice in the details, + # once on the spend side (negative amount) and once on the receiving side (positive amount) + tx_vout_n=$(echo "${tx_details}" | jq ".result.details | map(select(.address==\"${address}\"))[0] | .vout") + ######################################################################################################## # Let's now insert in the join table if not already done - tx_vout_amount=$(echo "${tx_details}" | jq ".result.details | map(select(.address==\"${address}\"))[0] | .amount | fabs" | awk '{ printf "%.8f", $0 }') if [ -z "${tx}" ]; then 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 # those additional addresses in watching_tx! watching_id=$(echo "${row}" | cut -d '|' -f1) - # In the case of us spending to a watched address, the address appears twice in the details, - # once on the spend side (negative amount) and once on the receiving side (positive amount) - tx_vout_n=$(echo "${tx_details}" | jq ".result.details | map(select(.address==\"${address}\"))[0] | .vout") sql "INSERT OR IGNORE INTO watching_tx (watching_id, tx_id, vout, amount) VALUES (${watching_id}, ${id_inserted}, ${tx_vout_n}, ${tx_vout_amount})" trace_rc $? else @@ -184,8 +185,8 @@ confirmation() { if [ -n "${event_message}" ]; then # There's an event message, let's publish it! - trace "[confirmation] mosquitto_pub -h broker -t tx_confirmation -m \"{\"txid\":\"${txid}\",\"address\":\"${address}\",\"amount\":${tx_vout_amount},\"confirmations\":${tx_nb_conf},\"event_message\":\"${event_message}\"}\"" - response=$(mosquitto_pub -h broker -t tx_confirmation -m "{\"txid\":\"${txid}\",\"address\":\"${address}\",\"amount\":${tx_vout_amount},\"confirmations\":${tx_nb_conf},\"event_message\":\"${event_message}\"}") + trace "[confirmation] mosquitto_pub -h broker -t tx_confirmation -m \"{\"txid\":\"${txid}\",\"address\":\"${address}\",\"vout_n\":${tx_vout_n},\"amount\":${tx_vout_amount},\"confirmations\":${tx_nb_conf},\"event_message\":\"${event_message}\"}\"" + response=$(mosquitto_pub -h broker -t tx_confirmation -m "{\"txid\":\"${txid}\",\"address\":\"${address}\",\"vout_n\":${tx_vout_n},\"amount\":${tx_vout_amount},\"confirmations\":${tx_nb_conf},\"event_message\":\"${event_message}\"}") returncode=$? trace_rc ${returncode} fi From ebb6ea978474df10d9455b54deae15f543c1a0ea Mon Sep 17 00:00:00 2001 From: kexkey Date: Mon, 2 Dec 2019 20:42:20 -0500 Subject: [PATCH 18/42] read-only volumes and chown apps dir --- .../installer/docker/docker-compose.yaml | 26 +++++++++---------- dist/setup.sh | 2 ++ 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/cyphernodeconf_docker/templates/installer/docker/docker-compose.yaml b/cyphernodeconf_docker/templates/installer/docker/docker-compose.yaml index 255a974..1ab1bdd 100644 --- a/cyphernodeconf_docker/templates/installer/docker/docker-compose.yaml +++ b/cyphernodeconf_docker/templates/installer/docker/docker-compose.yaml @@ -166,7 +166,7 @@ services: <% } %> volumes: - "<%= otsclient_datapath %>:/otsfiles" - - "<%= bitcoin_datapath %>/bitcoin-client.conf:/.bitcoin/bitcoin.conf" + - "<%= bitcoin_datapath %>/bitcoin-client.conf:/.bitcoin/bitcoin.conf":ro command: $USER /script/startotsclient.sh networks: - cyphernodenet @@ -191,15 +191,15 @@ services: - "<%= gatekeeper_port %>:<%= gatekeeper_port %>" <% } %> volumes: - - "<%= gatekeeper_datapath %>/certs:/etc/ssl/certs" - - "<%= gatekeeper_datapath %>/private:/etc/ssl/private" - - "<%= gatekeeper_datapath %>/keys.properties:/etc/nginx/conf.d/keys.properties" - - "<%= gatekeeper_datapath %>/api.properties:/etc/nginx/conf.d/api.properties" - - "<%= gatekeeper_datapath %>/default.conf:/etc/nginx/conf.d/default.conf" - - "<%= gatekeeper_datapath %>/htpasswd:/etc/nginx/conf.d/status/htpasswd" - - "<%= gatekeeper_datapath %>/installation.json:/etc/nginx/conf.d/s/stats/installation.json" - - "<%= gatekeeper_datapath %>/client.7z:/etc/nginx/conf.d/s/stats/client.7z" - - "<%= gatekeeper_datapath %>/config.7z:/etc/nginx/conf.d/s/stats/config.7z" + - "<%= gatekeeper_datapath %>/certs:/etc/ssl/certs":ro + - "<%= gatekeeper_datapath %>/private:/etc/ssl/private":ro + - "<%= gatekeeper_datapath %>/keys.properties:/etc/nginx/conf.d/keys.properties":ro + - "<%= gatekeeper_datapath %>/api.properties:/etc/nginx/conf.d/api.properties":ro + - "<%= gatekeeper_datapath %>/default.conf:/etc/nginx/conf.d/default.conf":ro + - "<%= gatekeeper_datapath %>/htpasswd:/etc/nginx/conf.d/status/htpasswd":ro + - "<%= gatekeeper_datapath %>/installation.json:/etc/nginx/conf.d/s/stats/installation.json":ro + - "<%= gatekeeper_datapath %>/client.7z:/etc/nginx/conf.d/s/stats/client.7z":ro + - "<%= gatekeeper_datapath %>/config.7z:/etc/nginx/conf.d/s/stats/config.7z":ro networks: - cyphernodenet - cyphernodeappsnet @@ -221,9 +221,9 @@ services: - 443:443 volumes: - "/var/run/docker.sock:/var/run/docker.sock" - - "<%= traefik_datapath%>/traefik.toml:/traefik.toml" + - "<%= traefik_datapath%>/traefik.toml:/traefik.toml":ro - "<%= traefik_datapath%>/acme.json:/acme.json" - - "<%= traefik_datapath%>/htpasswd:/htpasswd/htpasswd" + - "<%= traefik_datapath%>/htpasswd:/htpasswd/htpasswd":ro networks: - cyphernodeappsnet restart: always @@ -247,7 +247,7 @@ services: <% } %> volumes: - "<%= lightning_datapath %>:/.lightning" - - "<%= bitcoin_datapath %>/bitcoin-client.conf:/.bitcoin/bitcoin.conf" + - "<%= bitcoin_datapath %>/bitcoin-client.conf:/.bitcoin/bitcoin.conf":ro - bitcoin_monitor:/bitcoin_monitor:ro networks: - cyphernodenet diff --git a/dist/setup.sh b/dist/setup.sh index 0d58593..a9f8ecc 100755 --- a/dist/setup.sh +++ b/dist/setup.sh @@ -692,9 +692,11 @@ sanity_checks_pre_install() { install_apps() { if [ ! -d "$current_path/apps" ]; then + local user=$(id -u $RUN_AS_USER):$(id -g $RUN_AS_USER) local apps_repo="https://github.com/SatoshiPortal/cypherapps.git" echo " clone $apps_repo into apps" docker run --rm -v "$current_path":/git --entrypoint git cyphernode/cyphernodeconf:$CONF_VERSION clone --single-branch -b ${CYPHERAPPS_VERSION} "$apps_repo" /git/apps > /dev/null 2>&1 + sudo_if_required chown -R $user $current_path/apps fi } From 68b4059b3640e15986a53ea131119ffdf32ac792 Mon Sep 17 00:00:00 2001 From: kexkey Date: Mon, 2 Dec 2019 20:51:06 -0500 Subject: [PATCH 19/42] Dyslexic read-only --- .../installer/docker/docker-compose.yaml | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/cyphernodeconf_docker/templates/installer/docker/docker-compose.yaml b/cyphernodeconf_docker/templates/installer/docker/docker-compose.yaml index 1ab1bdd..6127dde 100644 --- a/cyphernodeconf_docker/templates/installer/docker/docker-compose.yaml +++ b/cyphernodeconf_docker/templates/installer/docker/docker-compose.yaml @@ -166,7 +166,7 @@ services: <% } %> volumes: - "<%= otsclient_datapath %>:/otsfiles" - - "<%= bitcoin_datapath %>/bitcoin-client.conf:/.bitcoin/bitcoin.conf":ro + - "<%= bitcoin_datapath %>/bitcoin-client.conf:/.bitcoin/bitcoin.conf:ro" command: $USER /script/startotsclient.sh networks: - cyphernodenet @@ -191,15 +191,15 @@ services: - "<%= gatekeeper_port %>:<%= gatekeeper_port %>" <% } %> volumes: - - "<%= gatekeeper_datapath %>/certs:/etc/ssl/certs":ro - - "<%= gatekeeper_datapath %>/private:/etc/ssl/private":ro - - "<%= gatekeeper_datapath %>/keys.properties:/etc/nginx/conf.d/keys.properties":ro - - "<%= gatekeeper_datapath %>/api.properties:/etc/nginx/conf.d/api.properties":ro - - "<%= gatekeeper_datapath %>/default.conf:/etc/nginx/conf.d/default.conf":ro - - "<%= gatekeeper_datapath %>/htpasswd:/etc/nginx/conf.d/status/htpasswd":ro - - "<%= gatekeeper_datapath %>/installation.json:/etc/nginx/conf.d/s/stats/installation.json":ro - - "<%= gatekeeper_datapath %>/client.7z:/etc/nginx/conf.d/s/stats/client.7z":ro - - "<%= gatekeeper_datapath %>/config.7z:/etc/nginx/conf.d/s/stats/config.7z":ro + - "<%= gatekeeper_datapath %>/certs:/etc/ssl/certs:ro" + - "<%= gatekeeper_datapath %>/private:/etc/ssl/private:ro" + - "<%= gatekeeper_datapath %>/keys.properties:/etc/nginx/conf.d/keys.properties:ro" + - "<%= gatekeeper_datapath %>/api.properties:/etc/nginx/conf.d/api.properties:ro" + - "<%= gatekeeper_datapath %>/default.conf:/etc/nginx/conf.d/default.conf:ro" + - "<%= gatekeeper_datapath %>/htpasswd:/etc/nginx/conf.d/status/htpasswd:ro" + - "<%= gatekeeper_datapath %>/installation.json:/etc/nginx/conf.d/s/stats/installation.json:ro" + - "<%= gatekeeper_datapath %>/client.7z:/etc/nginx/conf.d/s/stats/client.7z:ro" + - "<%= gatekeeper_datapath %>/config.7z:/etc/nginx/conf.d/s/stats/config.7z:ro" networks: - cyphernodenet - cyphernodeappsnet @@ -221,9 +221,9 @@ services: - 443:443 volumes: - "/var/run/docker.sock:/var/run/docker.sock" - - "<%= traefik_datapath%>/traefik.toml:/traefik.toml":ro + - "<%= traefik_datapath%>/traefik.toml:/traefik.toml:ro" - "<%= traefik_datapath%>/acme.json:/acme.json" - - "<%= traefik_datapath%>/htpasswd:/htpasswd/htpasswd":ro + - "<%= traefik_datapath%>/htpasswd:/htpasswd/htpasswd:ro" networks: - cyphernodeappsnet restart: always @@ -247,7 +247,7 @@ services: <% } %> volumes: - "<%= lightning_datapath %>:/.lightning" - - "<%= bitcoin_datapath %>/bitcoin-client.conf:/.bitcoin/bitcoin.conf":ro + - "<%= bitcoin_datapath %>/bitcoin-client.conf:/.bitcoin/bitcoin.conf:ro" - bitcoin_monitor:/bitcoin_monitor:ro networks: - cyphernodenet From bc00ec32fdc3df2fd520411bdcaf43ddc45147e6 Mon Sep 17 00:00:00 2001 From: kexkey Date: Tue, 3 Dec 2019 09:58:42 -0500 Subject: [PATCH 20/42] Fixed some documentation --- doc/API.v0.md | 2 +- doc/openapi/v0/cyphernode-api.yaml | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/doc/API.v0.md b/doc/API.v0.md index 93b371d..ff8633a 100644 --- a/doc/API.v0.md +++ b/doc/API.v0.md @@ -4,7 +4,7 @@ ### Watch a Bitcoin Address (called by application) -Inserts the address and callbacks in the DB and imports the address to the Watching wallet. +Inserts the address and callbacks in the DB and imports the address to the Watching wallet. The callback URLs and event message are optional. If eventMessage is not supplied, tx_confirmation for that watch will not be published. Event message should be in base64 format to avoid dealing with escaping special characters. ```http POST http://cyphernode:8888/watch diff --git a/doc/openapi/v0/cyphernode-api.yaml b/doc/openapi/v0/cyphernode-api.yaml index 5d8b5a9..91b9c52 100644 --- a/doc/openapi/v0/cyphernode-api.yaml +++ b/doc/openapi/v0/cyphernode-api.yaml @@ -50,9 +50,6 @@ paths: type: "object" required: - "address" - - "confirmedCallbackURL" - - "unconfirmedCallbackURL" - - "event_message" properties: address: $ref: '#/components/schemas/TypeAddressString' @@ -62,7 +59,8 @@ paths: confirmedCallbackURL: type: "string" format: "url" - event_message: + eventMessage: + description: "Will be part of the published message on confirmations" type: "string" responses: '200': @@ -1546,7 +1544,7 @@ components: - "address" - "unconfirmedCallbackURL" - "confirmedCallbackURL" - - "event_message" + - "eventMessage" properties: id: type: "string" @@ -1578,7 +1576,7 @@ components: type: "string" watching_since: type: "string" - event_message: + eventMessage: type: "string" WatchedByXpubAddress: type: "object" From b6ba329e7566a9b941b66adf9bac4c81070c6939 Mon Sep 17 00:00:00 2001 From: kexkey Date: Tue, 3 Dec 2019 12:19:59 -0500 Subject: [PATCH 21/42] Doc eventMessage on spend, eventMessage name in JSON instead of event_message --- doc/API.v0.md | 10 ++++++---- doc/openapi/v0/cyphernode-api.yaml | 3 +++ proxy_docker/app/script/callbacks_job.sh | 2 +- proxy_docker/app/script/confirmation.sh | 4 ++-- proxy_docker/app/script/walletoperations.sh | 4 ++-- proxy_docker/app/script/watchrequest.sh | 2 +- 6 files changed, 15 insertions(+), 10 deletions(-) diff --git a/doc/API.v0.md b/doc/API.v0.md index ff8633a..95ea650 100644 --- a/doc/API.v0.md +++ b/doc/API.v0.md @@ -9,7 +9,7 @@ Inserts the address and callbacks in the DB and imports the address to the Watch ```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":"eyJib3VuY2VfYWRkcmVzcyI6InRiMXE2czBwcHdrMm1zZGV3YWwzbXU5MGFoZmhweWVwYXdudzZ3ZGs4dCIsIm5iX2NvbmYiOjZ9Cg=="} +{"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","unconfirmedCallbackURL":"192.168.111.233:1111/callback0conf","confirmedCallbackURL":"192.168.111.233:1111/callback1conf","eventMessage":"eyJib3VuY2VfYWRkcmVzcyI6IjJNdkEzeHIzOHIxNXRRZWhGblBKMVhBdXJDUFR2ZTZOamNGIiwibmJfY29uZiI6MH0K"} ``` Proxy response: @@ -27,7 +27,7 @@ Proxy response: "estimatesmartfee6blocks": "0.000010", "estimatesmartfee36blocks": "0.000010", "estimatesmartfee144blocks": "0.000010", - "eventMessage": "eyJib3VuY2VfYWRkcmVzcyI6InRiMXE2czBwcHdrMm1zZGV3YWwzbXU5MGFoZmhweWVwYXdudzZ3ZGs4dCIsIm5iX2NvbmYiOjZ9Cg==" + "eventMessage": "eyJib3VuY2VfYWRkcmVzcyI6IjJNdkEzeHIzOHIxNXRRZWhGblBKMVhBdXJDUFR2ZTZOamNGIiwibmJfY29uZiI6MH0K" } ``` @@ -68,7 +68,7 @@ Proxy response: "unconfirmedCallbackURL":"192.168.133.233:1111/callback0conf", "confirmedCallbackURL":"192.168.133.233:1111/callback1conf", "watching_since":"2018-09-06 21:14:03", - "eventMessage":"eyJib3VuY2VfYWRkcmVzcyI6InRiMXE2czBwcHdrMm1zZGV3YWwzbXU5MGFoZmhweWVwYXdudzZ3ZGs4dCIsIm5iX2NvbmYiOjZ9Cg=="} + "eventMessage":"eyJib3VuY2VfYWRkcmVzcyI6IjJNdkEzeHIzOHIxNXRRZWhGblBKMVhBdXJDUFR2ZTZOamNGIiwibmJfY29uZiI6MH0K"} ] } ``` @@ -621,12 +621,14 @@ Proxy response: ### Spend coins from spending wallet (called by application) -Calls sendtoaddress RPC on the spending wallet with supplied info. +Calls sendtoaddress RPC on the spending wallet with supplied info. Can supply an eventMessage to be published on successful spending. eventMessage should be base64 encoded to avoid dealing with escaping special characters. ```http POST http://cyphernode:8888/spend with body... {"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","amount":0.00233} +or +{"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","amount":0.00233,"eventMessage":"eyJib3VuY2VfYWRkcmVzcyI6IjJNdkEzeHIzOHIxNXRRZWhGblBKMVhBdXJDUFR2ZTZOamNGIiwibmJfY29uZiI6MH0K"} ``` Proxy response: diff --git a/doc/openapi/v0/cyphernode-api.yaml b/doc/openapi/v0/cyphernode-api.yaml index 91b9c52..b7d7997 100644 --- a/doc/openapi/v0/cyphernode-api.yaml +++ b/doc/openapi/v0/cyphernode-api.yaml @@ -797,6 +797,9 @@ paths: $ref: '#/components/schemas/TypeAddressString' amount: type: "number" + eventMessage: + description: "Will be part of the published message on spend" + type: "string" responses: '200': description: "operation successful" diff --git a/proxy_docker/app/script/callbacks_job.sh b/proxy_docker/app/script/callbacks_job.sh index 27a2b30..62ff7a4 100644 --- a/proxy_docker/app/script/callbacks_job.sh +++ b/proxy_docker/app/script/callbacks_job.sh @@ -237,7 +237,7 @@ build_callback() { derivation_path=$(echo -e $derivation_path | sed -En "s/n/${pub32_index}/p") data="${data}\"pub32_derivation_path\":\"${derivation_path}\"," fi - data="${data}\"event_message\":\"${event_message}\"}" + data="${data}\"eventMessage\":\"${event_message}\"}" trace "[build_callback] data=${data}" curl_callback "${url}" "${data}" diff --git a/proxy_docker/app/script/confirmation.sh b/proxy_docker/app/script/confirmation.sh index 17b54b4..3fbfd8a 100644 --- a/proxy_docker/app/script/confirmation.sh +++ b/proxy_docker/app/script/confirmation.sh @@ -185,8 +185,8 @@ confirmation() { if [ -n "${event_message}" ]; then # There's an event message, let's publish it! - trace "[confirmation] mosquitto_pub -h broker -t tx_confirmation -m \"{\"txid\":\"${txid}\",\"address\":\"${address}\",\"vout_n\":${tx_vout_n},\"amount\":${tx_vout_amount},\"confirmations\":${tx_nb_conf},\"event_message\":\"${event_message}\"}\"" - response=$(mosquitto_pub -h broker -t tx_confirmation -m "{\"txid\":\"${txid}\",\"address\":\"${address}\",\"vout_n\":${tx_vout_n},\"amount\":${tx_vout_amount},\"confirmations\":${tx_nb_conf},\"event_message\":\"${event_message}\"}") + trace "[confirmation] mosquitto_pub -h broker -t tx_confirmation -m \"{\"txid\":\"${txid}\",\"address\":\"${address}\",\"vout_n\":${tx_vout_n},\"amount\":${tx_vout_amount},\"confirmations\":${tx_nb_conf},\"eventMessage\":\"${event_message}\"}\"" + response=$(mosquitto_pub -h broker -t tx_confirmation -m "{\"txid\":\"${txid}\",\"address\":\"${address}\",\"vout_n\":${tx_vout_n},\"amount\":${tx_vout_amount},\"confirmations\":${tx_nb_conf},\"eventMessage\":\"${event_message}\"}") returncode=$? trace_rc ${returncode} fi diff --git a/proxy_docker/app/script/walletoperations.sh b/proxy_docker/app/script/walletoperations.sh index f1ed68f..404a7b8 100644 --- a/proxy_docker/app/script/walletoperations.sh +++ b/proxy_docker/app/script/walletoperations.sh @@ -54,8 +54,8 @@ spend() { else # There's an event message, let's publish it! - trace "[spend] mosquitto_pub -h broker -t spend -m \"{\"txid\":\"${txid}\",\"address\":\"${address}\",\"amount\":${tx_amount},\"event_message\":\"${event_message}\"}\"" - response=$(mosquitto_pub -h broker -t spend -m "{\"txid\":\"${txid}\",\"address\":\"${address}\",\"amount\":${tx_amount},\"event_message\":\"${event_message}\"}") + trace "[spend] mosquitto_pub -h broker -t spend -m \"{\"txid\":\"${txid}\",\"address\":\"${address}\",\"amount\":${tx_amount},\"eventMessage\":\"${event_message}\"}\"" + response=$(mosquitto_pub -h broker -t spend -m "{\"txid\":\"${txid}\",\"address\":\"${address}\",\"amount\":${tx_amount},\"eventMessage\":\"${event_message}\"}") returncode=$? trace_rc ${returncode} fi diff --git a/proxy_docker/app/script/watchrequest.sh b/proxy_docker/app/script/watchrequest.sh index a28809b..0a49b23 100644 --- a/proxy_docker/app/script/watchrequest.sh +++ b/proxy_docker/app/script/watchrequest.sh @@ -83,7 +83,7 @@ watchrequest() { \"estimatesmartfee6blocks\":\"${fees6blocks}\", \"estimatesmartfee36blocks\":\"${fees36blocks}\", \"estimatesmartfee144blocks\":\"${fees144blocks}\", - \"event_message\":\"${event_message}\"}" + \"eventMessage\":\"${event_message}\"}" trace "[watchrequest] responding=${data}" echo "${data}" From 0006a98138e519069bcb3f3647b535bc44327cfe Mon Sep 17 00:00:00 2001 From: kexkey Date: Tue, 3 Dec 2019 12:24:09 -0500 Subject: [PATCH 22/42] Better examples of eventMessage --- doc/API.v0.md | 2 +- proxy_docker/app/script/requesthandler.sh | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/API.v0.md b/doc/API.v0.md index 95ea650..9aa9835 100644 --- a/doc/API.v0.md +++ b/doc/API.v0.md @@ -628,7 +628,7 @@ POST http://cyphernode:8888/spend with body... {"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","amount":0.00233} or -{"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","amount":0.00233,"eventMessage":"eyJib3VuY2VfYWRkcmVzcyI6IjJNdkEzeHIzOHIxNXRRZWhGblBKMVhBdXJDUFR2ZTZOamNGIiwibmJfY29uZiI6MH0K"} +{"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","amount":0.00233,"eventMessage":"eyJ3aGF0ZXZlciI6MTIzfQo="} ``` Proxy response: diff --git a/proxy_docker/app/script/requesthandler.sh b/proxy_docker/app/script/requesthandler.sh index 0d5c430..c40146b 100644 --- a/proxy_docker/app/script/requesthandler.sh +++ b/proxy_docker/app/script/requesthandler.sh @@ -89,7 +89,7 @@ main() { 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","confirmedCallbackURL":"192.168.111.233:1111/callback1conf","eventMessage":"eyJib3VuY2VfYWRkcmVzcyI6InRiMXE2czBwcHdrMm1zZGV3YWwzbXU5MGFoZmhweWVwYXdudzZ3ZGs4dCIsIm5iX2NvbmYiOjZ9Cg=="} + # BODY {"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","confirmedCallbackURL":"192.168.111.233:1111/callback1conf","eventMessage":"eyJib3VuY2VfYWRkcmVzcyI6IjJNdkEzeHIzOHIxNXRRZWhGblBKMVhBdXJDUFR2ZTZOamNGIiwibmJfY29uZiI6MH0K"} response=$(watchrequest "${line}") response_to_client "${response}" ${?} @@ -257,7 +257,7 @@ main() { ;; spend) # POST http://192.168.111.152:8080/spend - # BODY {"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","amount":0.00233,"eventMessage":"eyJib3VuY2VfYWRkcmVzcyI6InRiMXE2czBwcHdrMm1zZGV3YWwzbXU5MGFoZmhweWVwYXdudzZ3ZGs4dCIsIm5iX2NvbmYiOjZ9Cg=="} + # BODY {"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","amount":0.00233,"eventMessage":"eyJ3aGF0ZXZlciI6MTIzfQo="} response=$(spend "${line}") response_to_client "${response}" ${?} From 61ae9451cd029830af1a5af7d1d7c3a3d4c64ed1 Mon Sep 17 00:00:00 2001 From: kexkey Date: Wed, 4 Dec 2019 11:25:49 -0500 Subject: [PATCH 23/42] The broker moved, he's alive!! --- doc/CN-Arch.jpg | Bin 76127 -> 77969 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/doc/CN-Arch.jpg b/doc/CN-Arch.jpg index 3bb4e42d597acab39013ab2447b4ac5f15cfb13b..ff2f190cbd1ad18e2e03e7b2e94ebcd9ab3b89e7 100644 GIT binary patch delta 57530 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 G?D#)7nLDTe delta 55639 zcmcG$by!qg-!?p0gb1i0AuuQ)NJ6INK30AHPT&f6bVTI5ox5m8)2loyQRBp zfSJv^^}4S6x$ozA-tYU4?>*i>aCWTqTWil=ah~Vz97AU>&GcU?C_wXZWhY^rWYPXEc{%;EqCG#fe60+%6Lx%8;0Gij z;F;BK`om1wJQwcuLAz*`az3#3jR8g)%ZQt>!d|rk+0p{)^>4=sHkgKS*;I1ThV*4A zVI&SnPw}PCVIJieV2GQbfi4%Uwj$c_5vj>Y+`&h1rNrtc3!CAbSmG+msUIQp7JD!f zqR4ZY82p4r31kcTkF#<+DHN%d=1IbIXHBls65!%{4kK{%`d4ZH$u4p?S9B+ML_eIq zkNmM^CuLTdjM&vcN+9e#05Q(Ti#LjcJg*l7pToS(&tdh#Cg-q6&|j@DMqP;*KCO$R zJROlXF#PiC!H{)moXovL?AXFw_F#}k(gDvoY?ik892O7#%}8Kb&tV^JdqCG7=-Ehj za{bx+!H&9fSWn5T!|0h#!~YDf4E1vnNWdN@fT!?T9dcz{B-HB!Q?X*5lfc46?e}1j zRVU!heRM4!dl0h^pVhW6MRC>xJtf>Z>YyR_*_9E{RpBILC;4ATxpJ&v*Y6VW7uncm zgMZbO{@0QA7iC<`$3M*!YMun5fsNY+C#!->$oi0f9hI^sp9vB|Utq7t>->GHNY_U- zH7P~e?OIY&GA(;Kq9aU2wBUNDtC~c((zk;&7B2Q^^L>7$$4Xbc7d`oh zkvu6~k~z&^s343{IIE3)NZg1Eal@yXtfgmr@^mba<(xe;D@6+ot0j z1}8m8gT{&!)=cdjmV)g6)`Gjeo5o9XpHIr1;8yAr_s9%8l{)~gqu0kLFpLcqxMh%f zYeJR2rELhi#44f47`|*I>J=@*}nhDZ+UwDdqrkb7e zxSM%O-rd>hcIoOnhxI1s1@f9Foq*5AKO933*18Vxn%(D9lr#}i)*a@#_xT)_och|? z3w*2qo(}IfHd8#8l6;Bzcs=a_BffL)%bEjS%^yJua8Udx;jmMEEjbyAK|cUi`q$`@ z+G$p3NS0Mn4Qwt|5gSLk5MFw=Y+iWjm(eeqo*L8$|1^EY)8fxeH`|D0Z#NJ8kdqMm zCBX`?Fb>e+vJ#Vy*#y>10ju%b1F-4Wx9XRB2$}?FW0$F#@eLYspCJNN%}0pK2|Tkr zcODjhT_HE*OfXNUkF>F{57U_#rwR(2;OAt#tWwE9`ejjZ!!8)fmNkBPvMl7bi*k|z zZGra5)=KqbV!>ZzB(8YPY?Uw7lg#NX8zpFgn1#+Ts;@jZDW~z6<>nc7<#DBNj!e@v zB7TgR_9H$8dPj=*Od1*DBQ!>2!tQgVkUh3q)D`W&`>fxD=S%kzEWGs~V?d^+EQ%{p zU{8UrtMP-NZC2a;o@pX$DSX4};1@)F++w9BwDDT|ogaAE8Q!-&Wr!_kp(E9{KW*|Z z0Z>fEvovtg2P#sX@8glvMYhi4Y7O`35ZFKV{m%47XMdNRb>+!ST+}L6v6cNRf{q+6KYpPjAf)$tL@shdE z@;6$)d%pTiCzsGjFZpU}m2%v#=Bu3*c|Y*;bEDrz$6n#vLbivoj`P#ycX>d=uYHp#H}TYe8GK}X4zoc=$Sdof!&-7?wgmSwn3k}7=P>$k0F7g} zEWq*ueNY+TRAU+yU*6oAg?+2N^aCnz2OtARmN6r;(MvygfjO%=(o^g?j0VU88WKA8 zX~hTuJ;i8$Xz^-n)xOUs33H-O=AZ0~KDi1{rla)kHmW@3KFkZN%)bic{yF?wyUlbC zv-R!SH~AC@@)5Bn)u?`(v!{FK!nMsbT#7E9($4AEUFFr`c&BFGLgITn&e7B9!s8yx zFVQXj4pKX~ywod^E=E9+!3#exQ3d8km1l`*y)9;JHeMk=gx3T1omIBgYJ)AD)cp6T z7^8(=e;8;LiXaj=nx3dtalK5vT#)E&@H|GoY+(EF+t!_us*?Dr;Q_suWhwhnRvp|sUJ{DklldM_T5UO%&J@N2c&)bUV3wx$rl;O+ zuvDO<*W{Iom_o7nG~2cYj#J-dgz@8k8a|@Sau(oJ)O%uRe@iEzf@_wOz~R%JxF>J8 zuL@r4yced|ozwYFK|I})_G@zOCSk&_29Ctr*GnUkNW+^+oR%3J=Yn9q4fHHucklR| zioCaR$(WI#X(gx90Pf!=7!~?OY!e^n)*nJ1&}AU^mD0`a=DqI}^{x1w7TsMl&oUWD z`$)a=HW`ZD^o#&g9sNnYlYQ-NS<$_w!NfP<@udgN_mAiagNNCLwz#8LUxpnnOp&Py zj=w0r=+!JpALo&|a@ozw@6`S%(&?HRFVn-fRk3nRLF1;+FH8dLbV2Rd+>n|Ln}OBH zVuNvyB-DM2i-KGI8Y-Q!ulz2}i}{A?c9p+R^?i))iwAorbj|i&NOHK^w(w?i^>)wJ z+Tthbj=T&gLOfeGy~r;DTVqZ7+^${iR}zp(#dDRKiR~-WlQtnS|MEyJ?~B#FHjDJt za=va~Gr@~(&)@k1OIQMm?dWu@7Z-9WUugITA{pbu86Edg_49)wLlU$Ut^W?6@a*iT zfP6}3%aE>fm`+@UgP_8mh`9~@4NQtvZr;@lS6z>4r4vT6=^K5q&^?Qy+3U zIQ<;v$qRUE@1%d+bZ5LgRTI}()F7H9dE0QV7nB$-ob+8vupRWejHlUsc^AHftexnBGSe6<-W+qI^43Gy2Uz&84#Els;_ z;f*Ep1Cs-x^itz~ozwn&3XjW z6+nZi@J1D#*3K^K+*gV6)AkT*GkthzDqX3OX5fs48k-P#5`LJ4>@->m91bvbLiHtC zeMio!rw(k*Qaj(K&+ggFF@I^pKeUK{Oi43HyjbFY@@J+m6edPmUBaUT2-Lvi-E8Wt zhEHgk1VkL7QiU#>PXTIg>|I*Rrj+qtBiK(RP;HDsl$P7XfR zda#)vV0*YY-Z@*AtnT;n@lu}UJ$veTil_AEE~7dAkL#NF396&{w9a8>k!Qe6syuCX zpUBPDRkalL4wp0;z;Q+{^(YFcIaT4`dr&8;vh>)d^3OZ_fNvZ)U?*kzdn7@NHO$fU z_W<*xi&h>~<&%$w4bZ|a-9kR3KWMyI-;Rx$~8a>ixOeP`W904r_}eH$WeQ z(n`^ls1WW)`)YP74LpITUb@**FN27O@%_cY?jDc&(yo5P!06`T`L9Q9UlEgIm$*w& zw<<$Zw5#~-fs>2lzzC0Kc4&F+2xo3T!KP0m6}2J|M$ZvBFq1Ps6Xs!@D+#*6Xa%D?+0=v>9NQ0w!992sN&inE~3lmEBt{~om5+GA!Cu9D?YQkkcLyg7w@@g08!X#V2-b^YA{x!5Fp zH(P7?rW?A#KrPEbI2q9i)JsEeNAx>31OvfoLb?$EyXU)yoL4$CygG%eKTTD&QbX=? zR6xk2Vtk-t#FEQm`c^PQ-UAtYWOvcNKE0WL`ln24)iK8MYl>=fgWgFHBm2>isJ(z? zgfLPM#W`$L{v2kcy|%BL{!t05B&vQ6dj-dZqS8WkvRt{lsC(u@4tv*GQ@W`03?G}O z0A19HmwyNGyj3+Nz~op(|2_Ufs|Ez~Sf?3@}Ht#1(aD3;yQ|JH8+x3Bo#6zbmJ-MWih zUo(G?9wCZBkm-DB`rXx>u%&OnoQV~P$?HKht@?uZ9HB+pe`9@(m-|=8$bsmFRkKeA zo5aly#!Eru#XLeVlC(nK0DG@lKkU;g^({Yz|T%HdQxvUPem1QEyLKVuQEsfu4(evYoKEi!vUX0#b>#K6WdtF9jM z4ttPmaI4@<+UK4X=1A`~;~mDcKDokWaGW0qzW;(G-hWT_ZfV7!Luy%3yb@WypX!Dt zyXA(VcpqisxB5iAvq(mUJz?sqBa<^N&jM(GeN0?WlB@>KM3y(+IReA;^ai1Oo85vo zfdd7)2OIGX#5t)Ill2!L>Cf2kD z9FgUG*}$)=#+8k_!WL2{@%cc1*ulfyDQV!SJ2^hVEF6B^m;XASVXz0Qu#H}yQ^FC9 zToEyi{gxXVaK~cVN!u1aSwo3j59tpR{T552;G(BUpN!W=@u=I0yFbbTERQa<15{;{z=i$etOy0-JUTUc!d2M|H0|w*z~h#@`<2;y=df(6RFmgJ@3TqpqiZwX zOSiL&ARM@!dkS|61>JnBv=!2x97oyC#Cl21yMXXfR!KQQUxe<$kjS-&eJQgN4I`2+ zrAy49@fo^1<`Q%F67-p8PoextmT<8JS10{7VUiqD@Elo{IFS{sdjn@5B7XzPcx?c% zR$fd$zRdL-Pl}d+zw!i|S(6*z-NF;^8tcIw(mA^1i8Ql-Y)V&wk+I(jL~g!`%P!A3?2`ykTQW~R zF5#~b0nD;O`*VUU5|Bgwb12u>|H`{0Mx{?mHqa=7LNN4F(Mt&`$@RTp^O?Jj1v6|b zuk%0UI(|VCVie=Y#cpp6I^8o+s?)Sad(5@s4?dbl#td7hTj=a!L zb=M1YZaeWu{&O(+Uxh@!p#_)2fSJ3I2(dcn{|}}A6b?4`_^=b6BhMO+_e>d3vy&kE zpfr^}g{*(XTJt;1jpkF4m8d=~$5^r<6e3`D8*>zcgG4AD3dG&yd!c-?a;=W2*d6H0 z{8R=Z5CdRd@0wlhZD_48tNC-Psq_n?)NF8KbY42N8!IFg^3g@#FL{n{^9M)ghZUY) z&rUY`l=-85uNe329T5<>Gy6N*U)A-lpyGCxvEB|dr_dXs$pS8c2H)m)I!@Q@qyE6W z#!g4mv?YJoCNy%OL9u7Dg0C=3dRPu@D+gRp{ZWsx$W(Tss`u6)(Vz z>10jvKP0ivV`&8M4Ib1Os`w9|!{kfL3;Kci+A+7AJaJJ=moE}hT3sjU>Uc*np~3F# zI&F1JDR;8TN^D(*b583naUZ`G`>jg{J>MPzj?veN^|^{38Xc?F*$@x!)m zMwX}?Z)>C+FO1(G2M^##IeR-}T&?%8Q|`^67Ww0EpGfegDt>+7E62ybQn;0Kcc_+G z8|X0Kve>+PW9?$c9pb(%GA`F!xQ(EeW6JRymXG3^46et^sJg1M`Wf+q zBP;3ItZB-bX7rJLVQHVBQuJ^&4*Jb{{Usm~#FG_e z+j8ZT6GuOjO^Q)*HH_Rf4Apc9`0L*u5QMZhfEB#`rX7N|(iiu-+%)olIaNClSacD~ z{{@h&>qM_Bd}2oK4Uy#WlA=bt2J5#@9L80wtCC}5%4{_n@Upt`(v5G!wk z;*}?koDJ<=xL#nV$8w|}2SM*!=;KA8zEbHzl4QPfDWm#RJqbLumv~)5S&Bi@myUE! z^Y4gRI?tT;Zn35<1&-iVtlfub3+(TIqpxOe#yn_mI}n8f_1Q~-LsBXMKlHvuD`>@B z$M|dtIRq=&J^(*13 z{ z9wc9o3il137EkD|H}nU$y77tJGUjN>WMS%8g;vx6yH|tIr`bOb z{rd~RYkvVTDEVPvy?f@Q)S6oAfUhtYqXN96^|9gkoPPww8|z!@w(^oOJZyRUlv z=dgwQ7UwX%%T5#xX42rjvYEqPO&bG+P>9m%|0djGOceTH{e;@U7Kat%K4%_T%Uz>D zuPtG?vc_djHxugi=2QCj4%stpc5ZeyY1&t+;$8F&iJwxARs!=(tgrD|7JJNV+~#Un zlm;B+&!XZO23W{+!Hut)A_Oo8;r*Lbhk4SfC!!R)Z14QA_n55x&SO zM0ng-TAw3#OdDG0N8nK`PHZq={26wy-__`==Q-@>H4(hNHGP)C0n6dRCKm_ckGgM5 z%FX-&!!FX*6p;dpDl7DNN_Oc!|0wd=mC9Rd?jQJ?;r>DJn>j zP-TuHME+B%3UQ&%_Y05LsN=e&wI_B&Z)#(%ihr16@i#rdT%Qk>D=K*W`SAjc_}dSp z)C=l*itQuM3m;6M!!l$UW8)^Q4IM%iJub2$mLBSrPJhoy1qNxJI`U1K9cQ|b)4ct9 z#$-{gqV-}eun{<=!TDUfJ8SvsYikQt#l0Dkp)VI2eMEz0SV~{?%~kkw<7s!$7EU!h z>NEO6)^VYU?WI>YZy#y9=OY?_J}EN-_QujrY2f6bj zqU}I);rIJeE4#6M(MNF1W32EwOne&~!g3CyfXEnupf9jOcMc0(fNEr%dMmDT?(-3pq>L3oN&s129G;)Ry`oTU;-g%h$3MvW+J4LTp(4N+9L4v_P_e z)etCYQUzuSc9wA$(R-o^Sape?gInnQQ0}Wp#+G6!)aZZQgztQGwgcbKoX|tb+sHw4 zCxekz1N8)Z=OfcDH&W~!dRl0mb^@it&td*~j6lo7M?N8LW^^CvOIJC6)y<-VDnQ3{*o;6hI;jHBv59C1TpkI^qbiFNI<}bA+GFv#d%VZ6NoXmHH!# z5?;9pJN{3%Lf(kgZ7hzCbV=G+(G6ZBPG*&1x)|)sc%otTcjb`HQD8YRA(bc=7%qb| z1-bV{w}Ml?5ZXIc##NJIcf${o$CL_cVykIRk`bZ*T>7UAMF}sDY&TF#Q9QWDgsp@{ zE#RLXOOM=f9($_P%2-PDrJ8iCqhJ#se3bepDN=@Fc>*bcX21*>ksZy3eXA~Zj|&CF z?`;1IZ>{j@9{U0d?0L}CnZXRHVwm@^?Y<4%>zRGVztgsH}Yy*gc6U#R~-+h7}h81hB zoW(mblBzh+CY3x~V`hgI@qdc(1NCnNu_rB17BPAY9D{P{g1VPY5BGuv8u@`G$ZFG@NpP@B&!{y+2a5zVUk8hj~RVIl>CsRX5Wp~0yx>%ZedA@s6=VT9Ny_tU1Ss5>L5hgYAV8S{5!E4 zK9hP2uz{16DDa}>-A*{>QI*^lb1q6M-kyxB{_7>10v)oYx5{pRH^r)BS{>ewk70@J@m)5;ER zGlM;fvU6D8hw7|ZerR^xL>Zi}uvh1&wjsT_4SM%?6K>$iizn422euEo3n#CY-<>SOQ+jg$=so-k zb4%w<;faK2`xv#7=M1SA!u(;nnx9bOE!pM_9?Mlw%TExSrL*H<%Z(4i;{zPHAz|5< zu1L60R$%d`h!de%XpILt`@W zw0EOpSD?u7m0txt4lY7Qb^? z>2@A-MU~I(Y}6;w)+Xqx+1Gvs(vJ#vxr(prPRINTpc5TqpE0NWdc?h?Ju%r@ ztE<#~n<%>?FxIl0-QL^}nB@v}*Ro34EQr7TX|Y_@!n%h^G4w}*5F7aovJZTpW!7Jm z1Jmq-4hEOkC_jjx+`bu=A>VvhY-%Jie%8cRls6E}*SkLv-aC#F=X`(^lNWiKm*JxHl))9bkCf_%X^IVEd@JCVIu4N?;g*-U^aNs!Y!!iL zYK(QmwvU~;@~tc_^HTh!YC?PEg06S!IA$o9EQCbH#xCFUWUwOFdu)|i=NfI1_Z=`~ zlIh|cAfPj%*0@&g_-^6^D(r$6bPZcQv!!XxjHYmgGjGrHifZE;w9oXOTKwEFE(wbB93)Y4$7Yn72KS5m>&k^|suEjNQJCtJEw@IQ-6;6lw-poG*AeC$?y2((cs zZ#li%3irrrINJfAgp|Gj-oabdkbm6-Je2<_PQUC(zxsa?>;F$E;D6IE4^^Xn3k`vn zaE6=58OI_8$8OPNZRDT6X~`|aIgyBnB%tL7dixwEH`w)b-t0vbyU5k(WvnPLXO;(y zj9`XARKm_N)*I1tdJ3&${qRe*22siwBYWXweu$TS`c?@^-Ouq+(ZKKAWqw5AajM^) z{f=v+Eu{)a7a_YFa(CP^{+-DwQBCy0&ZxOvS}Jp9k4nmy9Dm>?av#D)bjcg|perz6 zYR7L}ei}zV6GcDl(f&W&49V=lJ7gIH3J64o)K0Db~(fx$zaebHj+(&bD4Z*2yC zH5MAfcK)(&QtvXGOgL~l)zCnBQOfZzd{M5sFU%*q7Wg?+>MBHO7DS;m z2P1v}uyF@O^czG*u{!7s z+xb}b*1yQc%d{MV*~QS~jQ`2t(YYdL zZviJ~eB+SF$Bo;L;f=?{OoJ>h)_liM%mLMV=P+fKv-A^{uc{wq z&wf8CPma3ImdhsjWM4D}m>;5({GGQI4nl%*QczFyHO^sO*_nBoO2}QC!-Uhla?nCg z3YC0wmsasuw8fvtR#(*Gjm>tvgSZD!Zr9^E& zdL>iDJ@iH@VRajwE2fM$5f_BsUOtCuM}jvXAh`_mp|{asscn{ZQY50k_?f}}yQ|nh zFju~?zipnNH{?J7c?8Xe+-DF-?M9yLS)HAn!-n%-zf%>9FhB(EzkLl*{zA8wdZ$_h zqrsa(P{_8w8ioU{L=u_d>=KCUii*)9ZjwT-e zQCKw@6GI$KZe|P2%z_k45llJ*$M0?NwIlqqCljnxE1_w%Q+NmBT_|W=_JI`YwJh~H zkNZ>R0WgMiCy!wI-ZWlMs??$s_4l)>eTBKK14D=9$pcgHp7=@^E{dVWN(k__w$o>x zT3qZFaOwZ?UUoH15viRQqvDbHl6O!{d0&oRWO=+eQ`@ebhd1-SrPA9v_0gS2=FAnQ zdz4fxHSFxIu*Z8Omqo5ic)zL%(eCt$+YM@v1yXfB;A1_SfjE043XsHRUu~|tDOXw_ zY&j2>{wR9d^`1ll#XZ}yd`ijv-o5e6@RUew6#Nj8^67;a$u*JdMS(3Pf)ZM&Nv|4= zA)h=hGuCDivW2`KNgRpC@&q=I7a$K>@WHeEC#(8plod&}Y%c@oTuA%-7G|8{|*>>8kf!E%dt1VR(5ynR;f{dTSbj{Utu~cQn5{#1HQtS582Eraf!Y_vP$!))-N0AJ(-yUkeE*sf)gXf{u} zjzBst$G%Oq=PonS#H`VCv+eei-4JDIzz=)xxGFAg$ZN8B-`sjik6(rDL|>PeA@nhw zi@3D#&w1*UL3jFDd%w4NR&*npg2$1~Pj6bDiDodZknb7leYXi7?Rp@9MP_$p>rCYi z5?-5%7#Qy^tTf_CBvboLLR1tlP2FNXl7oZGcuAFnQKd$keAS3ITYjmpbvs)Crb*}d z69&Vl_%3tdv%fYgUg;8W6Qae8ug+n*IwvD(no;f$fA}2Xi?gv>)*VI9@)1k#j4h7T zQukZE=Qb}C28G^9oRE;tv8%8SQ>iesn6oO!SCDUndss2!MHeSq`CR~BI=e%dVgTY~ z`T4uK8^id%Pzx-%5XT(B3`zWoge-XPsOhelc0mSJBV#ToflH1z{>R*bT72s<3n(G^ z6CP`3BNhLA?(27xVp!+1cME@Kdh?Qb{v>>_-F9O&4xZ#|WsWUSfw4Vs8+<}=*N3IT z?5Z3f$R-_3Db&`}+kboY@)wO=kwm?T9qZfoQvA0#3;5bZRi6m>`dzHFRmx~l)yb1i z0wE&w!DO58a%puVz0?=V$PT?kTKhF5O*X$Vb&B6-IUe0$48qnzwl${JjFDGy&)TML zQ|lxnv!g^V=rrJu&-VErpY0_l790(rlxjmW5@fGHYO7>_uL8bTPa0LcJ^JomwS1!% z$CC8()^-U}BdJSAIm*q%(H2XFWm_iOvv?;%edvoZsY3WnwAPg_&#Ar6{ zV*IaWF3IHH5`H2TbWfJ5TS;z3Y|88$hI|gp)akx^zq7A2@Vz2LC3qTJ66Fs(`AAl= zB84&p$$ChD-n$cfh@MZuq~mDg8E$#*eJ8G%-1f?_%EF^{rIS-H5G0y+=gwM%h|Q-%Fm1O*1VLic1y!thy>o(uWmR)LDb76|GXh;C zI;)>KxC%c84SqZRHK!qpod{{gXehh5s~-CV`Ek;`QnmjQH^)Nr7<(*0dwmdY*yUUnx$`aZTDG}3 zm!e^bZkULBC$w7ED!BGEps$+N!iNG@%f2%^6Nz><#re|pJUe7~saa5)xclI=+No;7 zs^vh~p!~=x&eYX{T-iX*rB0Ik`4vZWra;ham#UgfW9}O>&Yyv?S6ICZ6WleJ(E+K2 z)$yccO=c*7)=}P_i!;THOiIU+isG`O%^E>Z>RU{>QjF$KS$^8Ymz&dC3Rcv1-oz_v zM!{o1 z4B1p-3#!ZZ#NHZ)uHEmM>mm^%?s`{iF!pux1tT4>%V(id0q2<%uU}YrA1w81$bM5n zRKgGiI=o+UO0zev1XAb2qWR;z`0tf z>p#!%9ia_9x5{JVP$#!Lw{Q|L)q>#LB>Z?s8TTCmM@VaEPQ93cG))uGa~KVYRgfQu zV5bL%mkTh0h_(*jEpQPuj!bxibN3(DE$rC6spUMS_pSw>&Guob{mNtkaDb3_)_YJx z)oQ;^WfAyo`nm1ss)m0Nxb)j~%78LU_JB0#&DaT)Ho(Q=Je~=Fqgyi{O{Oxfo zAezjiP(oNCG?czRVQ0(ERDNxo=4IAx6P*uP=bFYQ(`?JC@dCM!LxJa;5`%yGev-`aHjOp5n`3t3}sCkF(aC2aFiwC>@dS&8-5HXiVG(x#;z7S>b$Uh})`190*dvosKu6ALdyT{yJMeZ-8r*jFj zTH1N#KyC*u5V=pNj~Gv3&?Wt;sRymzW$WMvYY1B=gNQCnWJ4Ina*n{LLbfyG1%(Qa zmw$H(JL|MldFIWar!|{_+E6MG(f#^iW&RCFUbPa3fPvR&oz@18eRCKIA{jW2zD zG<{o7u>Awmr;DT!lozThGsf?8Dx74FF_m(T0WDvUTmFUWIq3Oz0Ddf~6utO$qi*XD z8@WT0k*AxQ)16M7(Fk`}-G3G(I%*UmG@WX<8hvYC%AvNvOYvqab)y+gb>Ovrfh&F6 zVSJ+|>ne5GP5jx^f!WmVX(wKnEvkJPGZ+suG@k}>YL78ZP^*bNAtt1xvVOF~D>7ik zxfr5oN{(L{TK5S^^vuw>M5f5+`A9Z*Q=#S>Cw^fn%C`J@l(LM%MwZepOr~eRKmL2U zw?%H!d$A33@^=*XP?V?@!4;N>`tfQJ%!Q*T$FHTR*s;@j+6HGlG(#P|QimDOL22de{ta6>6^p(w1Eh=F&Wm{MNJuWG@lx>qy-BBjFt>=0KVS=nOJkdQfc&(bWy@<(6sCMKUb5_ zo&i1FSl5tTWYqYl;8n~2cT*2eOs3O}G z(;whJ__v(@)?5V9<_t}%#^WaV;W-RM3@ue62@){z&IuqI-CVW8J*j?R5Y3y=0$=|= z^4GQ#)|_0dGY$FqMOtvYR;#)oN|J3L<(I!3}D*dw1K;sizN7*P1ARs~@ zboGnoeFy~nFxa1i1i9ZlR~mb77wCZ~{!9;8>fw7-OA5U@d{8>#PW+*p{k$aq&kQFzQ>Ja-i0>g7~pqE z&7-uNesXDMsct3-dwy~+@oZuJ+l)DsTN{TY8ODB z4k__8ux1LIViQiTj;ht<*qi%DjeobbwdM(5SIk3!H!u)y@A`|khhXIAAx%U8T6z<3 z<_VjkEWf+vduU!0{MCO6J-oW27a}7R;S@nx#RtN zsP}!(q!7rRd$b|e*X*jeo7iJ;P8OR*4KD%4*No+#Qf-}^l`wev5jIvrDvjNL=;jY1Y#}eilh1QshWf|mV91IWyfIdF<-56)dLF5jJ(oP=awW~bI02t5CVZUg!A=QBtb5B16n9~!{ zjdp%J{)&TvFQt%|j8vkhKd;?}3w80gY=a0n-S;{vH514*L~h@${xo>U-3KJYV_E$` z;;}BO&XiD5QUtit2^-j{35fGo=44w~w#z%&uy(4rSf&K#cWbgtFzl~f(^in`e*C2( za1UR9VG|xTmma1uM6yyI&8H43VE4NE3;m#kQy-oP#Vi|H_^{3L?2nia8F!LCDB--t0E)9pV!H@pZ^EhKzW|?EaD` zKPREF$1k$6Z6WlP0hd_Kc(!q{$F+OrT8!@*mQvh4(K&`UNdjIg0NVo((nawbF55o@ zTxeB;I=zeIM>8Heq%IsKKW9FB8Y5uhAHe_e6y6S1hrM#n2Oob1M%n@G6~@ryx4CxH z?2uX83#?T=9qt*w18Js((6o6{8uqRm!a#l4t4l9jXrv}vqV05L+DhWt^8c$$NJ$;! znt7BAu@`R>W67x11fmeEH+nM!?9mU2-dFm#FMVgIUM3K*`zZ|KT-9Ai4Q8Pf?u-P+ zRKFym#~ft#5S!NszWU~ynX93JVkhRLpjoJeu3ng>oX$?2f16SlvFfW{UgQtoI7ai0 zP4d?>sZ`#$;OXVl+H6-^!)~XV1Ek(7ILyDUvwEt&QP$5CS-4tkPWE(d)LwGUqe#t= zVP7gVrzZpD8u6vDxp2I};w}X~Q)`Z*%h8;tz)Bw5dUNE_h^yDt+xIy(N-q@|oheh^ zxHZ;(uk+?vU9(GF?$sH}2{YR&-Rzz?nHPyAAGD|mgAA7Ol)Bo4s9T$byQu&=PKvMb zcLt1xf}?1|+icTO8)Xs6L1kOxdzVLZ%VTelDrx8A2k^%%eAAk`v3{}Vw!)>*l#CiZ zs*D?~t!>W^>61R+{-XI1*Y_v^>(T_SWEMZyN;nWg zPf-Y%J*wL>nKYjxxI;(xk5DD-W=q#29Y}RwZ|K4r0exhnT zFl?U>!)2HoWcmt-Vye?2}) z^1bE{My6hYA&SnTXG0YO+QGdNOGAy^Vw`WBe}x)4sXQ#W^wK2Z2V1ABzXWZM!^IXR zMZdcoVh}0^oMPwqSN+|!A1cs|@rEvU7J1B6ms|b*LR8FY+-*x6qGZA}f(91x4Ot|D z=f}g;phS%hPt_)iTRC?MQU;EuRy0Xu4=vj_2Awpqe@d|sh0wu zQ!9|(PdKImuW@AP-~TZ))l>lgk_T;9&nzBAG=2tpiqtFkvdLR7WvUSLh~AXu=b{Yi zj?S79Jmt$+_-xKx6%pKv$1)z^OIF9gVjq~!(qGKb-WM88D3FO1JjmXlR-}44^yq3Q zkIeP5ye*|!Z9(4b&-hFLWiG|796RmLOGW$uv?&fj)A>YJ$dh}0EQb0|#wpH^l3&Hxp9A0IFPCp*;qm1FXPRFH8W-tHkwTW?6-WU zjaZr88C+J7lUI#*U`jPI0ZfqC18rn+0_e(V+Na~882S29PT=+i)M|Wy^j}qTX;@F= zxav0ltMuK!ic1f!I+CIoMb3nw{X3}-2jwpJ`%qH;V(Ib);oYBDfPNyGE@rrD>L?Vm zjYD5(tRoX`dvAlKAoJ(npJ8}_MG7)loX1%sj?7^Iu<=jShw@M$nJucv|f zvJ(msk(p2E^)oCg&-dRzNEhle_f5TXgZS z+OvlqS@Ie4W^NG;Hm5`zId3iaC#;IceiqadS_BN-t8qQd#L0!p>!2~n?;Qjk6S6fy zkee*2i1z?M+?^~~4jZ-uaSXBzWj@t2A3`0Wg(iPbJRRt5TMG4DcclLl>?z&J5Hj3!^sS|p!# zb8eb$qbO#AGg!tl4)ttpq|?@3XZ{2T(pC+;q3a8=rH zZ?!kSm!^C0Q0HL4dqJAB1FBH<%~`l}@B8lR-! zYPi-zEjfctuC+u_&%4B2gmg-su1?!4xj`gERwT||Dmq?HR)&^}?`=|>3n%$wQa!%C zmLtAD?jeJ9Ug5W(Uuw`B-2Pvzy>&oTUDrMgQVIxybgGmRBHfILv~;(Gbf@G|5T%ip zmd>G5L{y}^yGy!bU}pTb_x=2y_kI8W{(?EP&pG?-*=O&yu63G{IJpzTUhD&y;+`GLh%>ZjStjGiFFTqZ((i=no&tq{AL?GAUMlGN zrN*yD>>5h}I6zdb>>cB^*Glx(GS~dKC=h7JmP1{}fiGo~5-T7?bT@+--JG>i=V8qz zCbW3Wo}RZ}D#{K7PVCdW3`&bQ1hVmOi9mSaHK~?ydGf{s7zMQ!hABs;%4CW`H@qUP zQ=#+7#gl!=AnjUq@wy+Oj>>gLZ6HfkuRSVaiU0E*tkw$j#Qkb)aR&Hf3(h?;d$iDj z(7NK{Jb}-hKNvP3Mkl#>mU}JhjJSZomH)j)5qh`*eG;f;cnxJcKVaOWZrz+i*8-RQ zpT@HgX(QyqG`Tf<-C)->dz>*c(FppL<%2 z%%SUq&ZAl{(vC|huHXqTlf34jX?_;e@N8EFL$T`}NVrBU#yvIcV4qHK$7;>Z2d=u; z%ze;+I<^xzix6SDZwVq}Uu`?}@M4YXQ?msm%;cmz|6o*y=fSBV zV_7IMrynR<$l5>no2RLU_v+v6`BT+-M^ef~-WC3ESwAOoBml=&x8fa|RD>P*Y*<0Y z7bh7 z#$v+LL5q~JC8wlUa9n)$W!gwgJ_lm@G%$e|$*5XUr}Q|BdzS4z&f_Y1P3E_muaN;I zei6;~xx6Vf0(`f$gEpC{n07Si6)EFZ?aTbiX)L|pNlIOvitB`voi~Y(@X6Hnk%bOW z@!)Mu9_I_jhl;OKA0MS(dkl!KF2M75pH0}%&!q+R?c)ljAH6+@hKLpQD<&W%(>NK~ zCuJ`(<-5OQ9r(JYX3877p7172j2gb(DCj%IjznvXy*T#Oj%YB9=w_v6pXvxqq*n_6 zmPWMybIhrwT{4`eF{n_xcpM+=4pDxBt*2Z21xgpL8YdOrP%;yh4?p_y zv8U}`jH`)rvA_7xLG=)Gk{bhb*Z@!^|E#U>hOV|1(Z>!#!D_emiurS!g! z)kel~ihax8`kPjFb zL1aw3byz-@b2l%*x)HR+(@kDb+-RM3gf=%AuP-@L4kPd;{N)sTq#w0(-CJX* zgU}?z#2^Qbeeqa8^{3a6{-S-Q;jgM+$!XfUpK0aDR(BI=!f)?3nArD+ow#=ZlEVkh ze`ZTLEp2qN8a5^agxT#c3t$SILKGy2O#OnKq(7oWKNZY6yJkGL+c`1zgyxH{dQW*{ zj#!TWl}#vDqKWQ*EMH(1IsB6KuIL4FTc!#2t2I#@_~Lm(Wf3`+PM3w207Jso&6CNq zEg)l4tz|CygOS96COgWr%`M0RQO!VIB*%ij1juZRZ9i)~7a#4T{;_mDT4R!kN-Sik z|2GroZN6<=eh4_Vo&!y8I&}t1A=q)<^w19r0lkcqGRF5i0{ao?w9xn zPJzF#CX1j~Tfk;q2DAupn}?!0>GK}fntw1Hz&#yg4GmhF3ThQdR7q|+#IQm1c89+? zMqt(agI9P4&|(MxEunLhEU>ElCmSd`Zp_q{M%>a=x*xoA!`vRT*p-d1s_O|KnBNR@ zBO8Qiq_WhF-sfzja94cKtrE^qkYg7xjndn9vZp_KvXJdX9I@9uXC5ar0iq-`QG?@ zLsGS2k74gevr3wsl}8P(hZEtJQgDc~_EtjDl}x(ZnJD0iKb5QR>>5mndxda+~ER#q3-(3UACK{$Yv z*a!SdQO@{v7G$|qW!C&<(edM-xbbYSfsES-N4#uG2n1WUn*x)ZcNKkYu+;kRdH+Ox|zKyn!}Uf~)+i}rH=^RE#l@&OnS6y^c!U^o{|;s~MN zTx4DR9^NbcgCQ>g*K7g08l@H>4&EDcy=VXN=tEfc5!Za}&@9w_GX($R8r|AvojL#q z|7sR|me@pH2`FAGw~QK-o=+Hhxh9f6G73)_FMT*;c=u6%sXY8I|EZ}@PFG@u7(r+Q zoz4Bh5P%~mVHH`I={8yaek}5bK;Ej-3fN)+@*VbRyR-=EEWQe_N`?RXPtksO^fKh^ zNMP8}PZKA{nt#pAc+zdlrFz8)?TY;nIy|YwitX|bYa?pyOj&{z5k_`SI413O>yB~dM4MmWyF(b*UQ50N~ z%+H$jCtm%a5>qvBqS!&%Ng8@`KRr)oc4!DFlh=K8TrZlArr)0Y_riJ;YF`B>Zje3f z>}R&1TUv$XlXN>%Bp?4YaVu(IcmJiNSz`iMSdDSlPxM(jun=j3fj~sNQ))g$+Rt|TAc~`C;4%o}RzLeFa~3oC?VW~U1z*xkq3L?lll?@4HO5s!(5{_vOmT}sjMG?OA(a{1kjzNNdD*kgNK>!N!&^!M{z zBbWSiSmU74I@5n+A^Zn_clU1;_CWW7x-7C&{jdDRv{fVr5Ja)xa0ncl&Ozo_t90e(+(jnU3eW+1EX_nK-O&VrWq-)&XyZ8zdH% zEs?-_HrBo~$2J`E3=H9^d|h(@8k!qy_o&bkoqf-Y%|v&N}DED~{Znqt+)5 z@M-_4g_loB_AMI8%-*QHK5I9~>JTlRw#k&QS2*)X-4=R3Vpv6Zs|H^~5GU&*oZtq= zW^~H$rNeKD*sQR}w)*83Uwo`H)?(Kpp`dp)r1G?~c&c`m3#22=kq>0}ah#Z&qW1O~ zgu)gNcG;!W=GNt~(}9LncjY8fx@4Qi_+wrDXkKdPp11i1p7pb82lmn?*XAO&!tC;a znmrFcF{MvV7WxLS;po0 zuNj#lXpZ|PL(=`#5gAj;K+oi_NYd*G(N3$>%{AwI>O$-j@($N`TnKghkRH{GyIU{% zZ3Kdn!h|v{=FjY*57sN<3ubq#S(u)i6sL4M&;9{l*Q zzAr^W^cG23)@r!|dpj*tvd6D7!KEn6T%jO)Ebm;9#MJdeU6)m(E-W1_=2{4dzi_QY}go+01ygkWPCXD;=U z@pEu&QcpiI3%WN7-9SVkA0};($zMGDQP_!Xbw4&v&cL#YKdvNsmFx#u1ntTT%$dSs zwmknwR1rE@RS{rMwXTq<5WZ9-PAnyTEvd?QG4CryRiQ+TWcC`qypxzA*j!-PUeqfv zlC#x%bkQWwgUtRwwRtbnLgcpyoMsg%oy)ZtSu58Y;g_RxF2Kn7lX0P~`8VQfle{T= zvJ_9PO_+&^3GX<+LZFsVfa66#A(|B0OTN|x6CuV_;0glql*Dz%*;z5y6V2KdS!Ljt zrR*!CzBRcy<+3B>!N!KaFCqA`JH~`(ww-@91!(4aWsY|C)JU=+;OP%bJBpg;U931u?XTbE9g?<30v`typPAhVcKvxLR4f zBKOVr(QS`G;M*>qwYQ$Kx7|ewzA{|0&Kg$hbtPkYUi~0RSWT7rn&^zxzOSm?Z2WBn zc(Y7_EmOXUfIDqe3IO-$ZUE&!E-IWj<^UN{ctn5uuetQA&r_!rjK0MgS;09p?G!jL z+EMe8KBQ8a_Zlq`Gp5ZvD#KDN(17%lL+>f6%|e~Ekm9TD(&-m)CfTw)N;L&CgI%4m z8X5PB7Py60ICFzxq->!t5^%4clsV+q_cW9T{2fi6GMzz$S&4t#w0T?YU)DH$U+S zidHcPxg)nbr!0hUUNC3Z&PfdrY!-uUd=W0B@c8pmvZhx7>8xyUp8~I?-m%tFQU}kr zNV&?nf@PK3@X9imdhCp#wN;18oCgzK>dd1@P3gOCt&R;$!I3ydsjhiRt7^u|i#6@2 z#fcP7o;=vTjaAmnGmVKoztQtayf_p6xQ*%&S4jv>EQ-Bku}Kv`tg zKL@sCQ(XKCHx0J)za`$I+L|bSSsXDd-(~#Rtb0n;EH#CHS~pIsYTMl)IGj%hs`MCY z0dRMv@^)(mha(D0g zR%{xGMu z`wfF6A{v3bAEr$-2G`<`xP#uhq#Dtu#9^`KLMj(!VnFaq7kNtWvP}URG)$G z?^dGs;LL$2s6l(jaR7ZT$1Fz1^^L`@fkC)=SaQKjjs{_Ecf9nfQ}r3_Al{g~Gt4st z1AY>72@Kc}{|oz9D-V%cP9s09#>^9px*4HQn^_iJ6^>pfj@mudaafK{xl@x?5-Lzs z6T6V-Vg>RpJ4MC;Pc2Sd0lO+ z@()I@>D9(!PYH)Q?=%>4Vie?x&dDpvLQ8@;aye{F^TaT}^xh`reoVfy6XbKL?RlNM zrSWr_kuBfvU`5h(-(axkEUQS5;4NL%8y0%Gi;~IAW+V4O#ojT`ywI{caYin?Vt&NF zDFTkq_Z(jg$9gSA-gy+8HP?G4aHmPiWxzd%M_DOEZ(d%6E&Nwsttxa?q6Yb}4QX3= zeCS;|nn*Cd=PfME6pqmcA~NmjW`BK6;DF=L4ITg*fxy5Z@GRe@b}b?62Z&Lp9FC94 zH@om??bfSL*^TkNeSNoGdYwkTJ{SG+#S@z@9L^ByZ*DHo`qhtfi{ZJ^Irx;cTEoqKrW)G!^b}uPR1@!<5zTIQ3L)-Ip$Wl zS(94aVtXgP zwv2ba1-I}|Pbw_jRODc5H*wQKX6vvPECmaZS>8}QH=_WkoC_77Q(?tS$N zz#ZFKyG0O?SBhx%I!%)_ZGGWgRChC%(l;UV{`=qC+}Zk}G|V42b~&5t$yWR3bm%MQzT;_m|9b7H*rF3Rlmm0;WTCOD0+(UmU!e4(`ZpSQ>VL} zz<7CvK%R`rf<9AY>Z4Z-FWIqeLS-#Qx|r82B2SEV1}z}Y^Wa}x-b@W4d-PH##0TB+ zYy^KWxZH=%cET~vQaQRGo80vlH(?c04ip!%)Yk1|Hf9uaRck#Sby+%FT5a+5iaBrH zRU+OaL^bRVqjEtGn$7&x#+%2JF-YsW)a`3kd1sb-Nolt?j4Mcxzh&~nMDxfR{VdfR zq$Qy6E1_RvnqT-z_U`_=KjcZP{QSiv;nJaX4B^hcG^99$D2y z;5!H+QpYqN*`sa_{=Vf|_Pl@}zaNGRMJwn-1=d8<+D!YzQmQ$XO(|VFqFku+I;LkK ztM~l(pAzgH=)yfBdI?@yjahnjp+=5#k6gv$ zEsc$AkVh8dai!z+nZ$WD*(T+?_MK`{-!si22A?ahvIL)uP{rNR_a141bI545%`r$T zrA$PHJU5B_;RP@0v8=J~=-WyDE^Yr(sle!$B*K^pA8Ea*85*}zTdl{+rJY1Z^jT#F z%wNK@G%(T6HM=n^(=qk3nT-u}JuU1v%Q5iYACD3S$N}YlQc)AzW2%l*kC&MAzTqj) zCRjS!6Ffb1bpXGQsj=u+tb%`)BP(Q+AYVw&7u&gJPtdE=P;JC+cvIeSs0Ndb9zfF- zFoOn8$~!WWlW_{l3HKB#gBHb{R?EH4clr$qniIP>JSd@n09Uon+OFo9-c0*j*^zn* zynS&@szBL|;`s&DTBe9FUZJ8T#ZZ@{xmpEvswVsAdxSyH(wB}pP3=^&458o;nnjIK z`1_$mqAO+Jr9vbVyVlb*<)ZJ?=6{#27o4x5>UK&~&F2+U9-e8IybpNID$(+i@X;w(zOR{Pf#faQ#GvCAD@2`#wU7xO75#b z*YUkdoF>fX*QpiR7sv5a?sF*MnI3N7(r<`l@T6>(m~gxPs-@X7#~J*Hq4JJjJ%4PT zkdI73rOt3eLajAY4s8>)FNPE)J)(q;4-m43reCm7?O>A&>gm#_o|;n->${$w!%*lD z)2`l~*GqML#U5w(OGL2jZ^FNf6jqIlU^vDu-#>Y_8)X4ug?SKTCNNT7Oz3dL0wPg=$*KF&SiLoG)}1~T>(Th-xalS)}h z^@aCt5_%cEy`ovb=U(A+JY^3E_tN`XYfJNdu}&)^mzfZ)^{cKiKCp|~@x$#9aYo-S z$J5>7i=ubJ!=PIq1xG%=Oeb<*%+gf#OS9c<QV}Q7-q&|f zGq~onpoB18oN+~rP3rI&lFriSV5+7xV0|P$Yu8gKTd8r0e4BYY&n)6#Rb(&R<3ez~ zoK?RK@sabHM4`yfm=`b2qvdB^HoW-?VtNt^6gou9#e5+xpW=g83gz>wA{w~FOYDRb z8V1c|^@PKzV3Y{MfB(VA={_wt)tcm=r{U&B?+d@~(s1iPRqrlOI+LD$@!=ltN5wee zB{{`oxW#f}%Wj3MbeBClwHb^w0)nFMu6XSpzhMremB!Q#7Ox) zXy+KVHa=nXWxL|sH?SH-n2S{K5BcX30Mw88r%Hp&8>5jE#jx0>M#ywiMdrk`fJ}3p zAWn)w<$PqBX^jI~6`ShAPf1k+yMCkfgpFJ5V0$3Q zmMMKwav`^F08b6mO7%;~DKmfk^;&M9vhbqRiduY%=_3?LsWx>LzM2dyE(g_OgqPY6 zOa!uyu&se!vhMWj!b#I{!}wgQPf0tCKK2C6yCh~&(o^)#F0_J0-yKlF&yY7AT)0!yn zy^nDeWCK0pJupB1s&0qbbuE@Prqf4P#ivzs1ah@JwUd5uzFle?oV}a zD0rMcHpW+xmRF3vwWOUf+=knE{mY{y+|WylE?k!7Q=Cs(7h6LtQ#|)2jb#~}iF}DB z@`ehoLLq{5RqQr~_a6*G0Y{h;di*+IO>n_FzJ{RRrMaqAcyLTNhcW(P)jg=;{r#Wf z1&77VWw8g)VbW^|c1&FjkoTW?!0=0xW@6H*?L>K!pUfKel}Ux2pts-;bK`qV=NnWN z>mmZewO>u0A6zOTC1D#puK?Q$Bs+j>po0ZXFD`Z8^b&rv58MNUDvwd|6lgOZ@FrPf z;NO1iQ!|A{B84a40?E}k2C3|+5{>eEXeS8tA)7rg!;d>ktp7gr+%;&M5a zJoKq;6Cjb2BNx2879i$3F>Y=TR9r@2JHQ8Is3mn z>tu@!Z>o%kC)LPht4{2BjXs1=AHXt#8ne(<^6S}7H+?^uFP^Kkqa&m7O-dT^nK z*38zGW&Qj&zB8nSI9tKtvJ?5%HB^XOcnifJwLZRnQ0K0H;jh}6uk_63!{2T9K2bl} z%tNe-e_6!+Q&%Ut@-XpMDTBI@!g)D|5o#g-?-n;|VYP5%%cZ|HJ;zAf*gM<2=@9vb z2YaHhZyaLKwa)A?05;<2HRkh{V5*AqI)wpUYsnV6{8q(~trXa$04G7s7q)KTr{~R- zWE=r73Z-jBVmV|HdL;PosS;&kA2^#S-DT~P&l4D80BIs0FBh!^niJ`JJq9|0+{%RJ?Xr1pRRh>7~lz}e=iHI%l&)Op4ptptEtR?cf~S;R*6&Ir@ynS<*3OsQdfU< zD=K!3jVfv2!M3EgGz;g*je%tMmfiOyU1puK;piNoZKP{lWJ8hLb!cr{h6D_Ja!otahsm zWl4VnCe)>sAO8Ny`R>+>*?jcA#fjD|>8J38uO&(9v?xEW^!-e?-JTJ;iUa)m^+EcW z0uH=ujZUSbiGqts_1Utdp%7j=Yd8o!{xtjy4H-OXujm6 zS=iq=ew}HvTp!`{>REP-8m zlxy|22m~+7uMIywtKdS9)9DORxZdf|f7>B)&!SiagN`Pgo62y(zJ4Yk@$2a9+!Vpo z0l8b60;Y#*@@cl z{f3N*8OMiqd~=iY%4fXA-*-}Mj8;OEPe`xODrMLy zvoCOj6+}2^JdnF?Y-2rR`Qj#O!6s0~Q9K-SF5Hst$H2Dgj5~X2zsowZqW^7p!|B8Y z;%RW=iMc3EjKg_Pf51wNH7oF&k;eI6EzSMRSCKqfw9$O%@~@cz)~b#5_CDOX$Fsvt z<)R6Df^kEe>0DeS#ka@deKscA%&XYmc=QktBD~^!_Ep>i=9lF{meE=k;%K=nyMdq` zS$%2Ru4L>6P9it=Xt~O*7ecA}BKGU%(A{3_#$^{-n5I&!m-+g!)!tZruHP-$B^S90 z;nm43#qgf(M^5-0H(WcvJrzOO3mT#xjR?^M?Q66Lx*n6dxpqSSV`?T_f(PHk8rw*M zyw8@1Anvgdmt%9+W7c%?BY{e@m^s)9`7kW`JaE>v-Tjd_QT__8^8?F|`OQIE&~^B9 zw78S46n;yKO{!gQ7fXJGSL8PxT)u@;qeMRX)_i;m=Vk)N?7WlvxBH$m!mSkC7D#BY zVm@6I7k^%tqJQMBZI%Ira8VpeIrV8N+{byd(QJAty$86z+osoRx=c&Z3lw65-9~183NSBpPy63ja$yjvGgR_{KI6GWzr-RLR6G|whJ`}D z2k38j$K~QyG>MdW%7z*l`UB}>S<-`G#|I|&Af1=}T?5z=|NpZHtOX*Z__N@q8YD$97^{t-j6P4AbRPIkQ4Z8rXuj?1&m?k;CKge*F zV@}|BZ%|romJwP&<0g0E$1e1wRS~)IER;Pi<}JE0SvZl`Al{AMv$>C;+)~yfNkNsu zkXKm~saNT{k*%-9Bfa4c!>JZe94;QjN#O}pkk5#|>9qG7Gx2Z}i5kLC;nUuT{BjMo%}x6pk`o_S17S9PEcoC62xntz7#jhBt}U*+kYlYeynPLd=Wz3j%Y0-Eqz zH2aO~VPD8y4B~n|{58wZTm4aw8aV1Mh~8(|k4NDPCcbSU@c65VYe)*xMeSkQ6J@71 zD}ME^8}+~RhN>(gPs!CI;%jws9J%!KW!&RuTIeBz0E3F->VQ1I40`eb=%+21QiEa# zNNngZ7kyWm8|||QEir*5kWr@;StjxSce6Ao3Y4)^IxXHwmgE_g9HQ-Q>40qlB>pGp z$GcH5Uo*WIFK+^Fj)Gaq&VJRG*nJNVT@hf}79@ir6$h<{B%p1~r80jjViQb>H!)~3 zG+Y;<*8+Nam#~;&TtDniX>@KlV4D4(%c;R4`P+yVAIJB8!@p}*@&|*1rkG(<&iV0j z13iwaQGQ%2y@86b-NXbZy}Y;Jz6Gs!bWX`e4f9cEf`vo=Z>nuaQzQrH>rDl-kI9z4 zp?0r=%jUb{U0}W`f(lh8mLVAbww0>p zTPp`9H0x*Rvu>4oGmq(wF(%q0Yx>;uWD@{7fyp4lT>0p6M)5+LJYlbh#`3F>_Bi9@3r<98tIX=pH`6?J6_$~8*%qW!@lbua>cE!9*|o5r})(&e1-wnO_| zvS|87zzD@1nd8scr)%gHMP#{k5>SSAn}| z^D5Y~WCZ#xfZrjxOR(zPIyF5dhOVz*KU-od06w2~A|er@(gp%=_}eoNY)7H3NkLT~ zoDWl0hHtK^@D|GR+&?O;e)Emg`r`erywDpLm|YBAJEQy;5O|ULO;C)b!Hq?UGLH=6I^qJ z;%)e|^+|k_MobuG5nHNTu<1{qrXiR{xpvu8-c|&vFfEXH4Q|0*gzGonm^wzK*mBroHjJ zIA$c?`6mlhXh1o)avr-ca}+r46dg&e6>@?;97LW}a4SnaE0-1G}#$wxXIofuMhWDbY1~+viRC z?U3;<%r}R~HayIqthXIzc{@U=-@NooRcccE`Shj2DgW39cflw3RYf_B2XJ55U|8%K z=;}&w(x_8=&Bl_(HfAZ7QNgpBf(&u9`(?TtQv>1ram-u=MzXgyWSI!|eF_)m%-6A9 z`AX9%Ad`rFyYY0h#PrOs+WNYdk)JooW{uxgS&lQ;ggvcGT4ke(p=Qi*|Hd*|VHr+k z@qVS7mL<%A^1N%$aJs0lA!rNKFzCwtNM%i~wf(SmhW)O(Z*KFYh`Yc<%FC0U5#dJ9 zxlT4lgY)fU#`l$-H}vJm>_S-`*dy`JX37YZ#~^z|%9fUQB+ahnWtwX9xVVI;wZ+5e z73FtMu^)wr%DVJ@)OzsaM4igb7jAtP#C$LdpH`(lpeYnf|3iHl}gBwcZ$; zAAb|-ZSG@kGE4k1NVOEH``JfA1G~v98&aNY z0&&JLB+s}Q&NLD_j`bJ&=W*a%D-wizf_d>svCf^>I1>Lo~a^9EHY+U>l zr%t_@t&h>ErZwrrPtRi&ZDxmEHP66=Ot}FeQ9Re9KQJaJXr|% z0xGX9aw6EgP2D?RxJIhs;s7OP?%lIyy__%6+n513A!D}Y+o1<_h8OLXdK7pVF2dp` z`ZEFF>sm&gsFiNk89Vkw7n1h58vAFG4O@-fK23_W5+d-7kyPb;k!LQ&u$TQ!y^C6c zSX0M*=yb=&JW1?j1=*;|2X{L0ycA-S^%!Wg<00022DT%K&G*g+FRUIG4Cl8*uj$vC z&pgdeBuhg4&~TY^Yw95408!deRG5|XW1L@mq&4z)o!t0F*%0G~ssy$aJcDEu$DwRt z+4kN|iOVDn+IrLBpU>6U(OG7=DHh}z4QXq?o&@P<>R^q!$H|{=Mkip}eRW<5Xq$zk z{5W02cOO&U%ux2D_o4iy(g2+uat!pr))z0M?jNcS9g3mc)taoLroOij+6ZR-0`(uc zRF<9vU-Pal%Za;RjZg16?k77O<^`j&NAlIkpS^v9iz71jE>4jx>rs1N93L=Y9>ac9 zx?e$2)(dmJo+Y+vdn~~YUwuAp7G6TSQmNhJ5KyoIF0Ll(k*0l`%;ND)fq^!NFKS}^7QG06(i{G1i>|E~ zuZpRBNRLHRiya`pOB%=TxA<^G$m%;UTHof6Ny7)P1}~IFoGrDn@Lknsv(8%gQ$O%y zW()Mn7CmKr(*MjkuTv)P3#=K6*wS{F#zb%~Ai$4e3JP$)%+N~RwYz6kZ{3A3> zubEt({ zSS|Kb#%-N)0u4)1&qeApx-qX-&u?`Cg8WlA&qUxNo{bgR0Q+XOk?9w^X$10Eh!I4u z{m|clt^HJB$f~L0j*&E~=`;Eq7Pr;l-?ok*HdrKT0q7kv0(j7@wiOylpv{BMo{Kx){?OECU>cL7v#$aoqG;84ba zT<^znpSM(k(UK29ZwkI)*STBE?*m3=^YPe#23MV zJ-pHkCVwfX{zCIOCZD?EbJ8xtyQzvSNWKj}dSry-;S04OvMMJPA2!yK>HfNNhWBto z!~8N?{>M`0i+q;Hkh`*j##GinrJmnBTqr)l!tfzCVyeP;)O}vs!0Oo6gGawjeA+6l zG0b&4F_DRc2oMMsvis$^)(0Qj%UC@9?8ul%K|meBQQ%cYU69n>4L9Y2eSxv}e#D7= z^ZWK~oOl1gZzGj4N|#>W33z{DusSLGwq_9{&s48}>p}xF)}hv;T!90Xoe%aoZ!wdk z0>toER44LE28=vSPn5wJU4og%{DlOIPGmOuBmMlTi$zdod^_u6sL%(X#e-UZlj>jN zAz6hOPqrvgr*_+~ldNT&YYLghCZBZx8hREK>aK={zF%q%RjX+-sC}|xLUS(9i&6XeaT9IN- z{aL42H>Z_rnQ?)q6KTdmZCp3i{m)=e)Qb;Kmt}x~`PB=|{_?^rodSCfZGh@Tr+`uh zs;BTrNKhTO3slTM<`q@*J`Hn$`vzleF zr;t$8x`ZwDBy{&}(UA#*r^F-6EgPh}BmSW)O8hHVsq$f*;eS z{Zrh9zGCPxYS@sT+|Vycv3hRs*_mLsSqP4Llq9^Pzn= zpIm_n=qHvRfXk9VISBxN&@Lj74ag`)jS6J;UiRGDiHX8$3(NA6o{i>%OOAW=i~F&N zCA7dP40%te^#tLJpcx#KB=0XW*}BjG*@`s@U0)g8L_N-CApGs08Wqn9g}9|ftKY5K zr%z-YOBHYGniMZ|~W-waU_CXbc=h4pN_ z|8qbgrdSuP_MiTam|hC*s8*)#?PtFV^Cnkwvrp}}&`8*7R@N)(Rx$@WEF%*N1Osou zwy;n$nasR>vE`Uv_A83Kq3(lw5Bi?%qSN8%Kxqn>ks@sV_u?QdI52Bx6298_*eFi|0l^b(q>|vOpA5gh}eJF9; zq~AjwDLyt~zOg;Zc<{G@=KmQo134UH?sv%s^aFtQSY}6|VLN#|qu4V@jgk2K_25~_ zbXmHA*Q-KTfI#sX-Zy}~cRB+S-a$4b6w=hBchlbxn;oc-tqb*%d$F;Qds)P@3^Nkz zENWID^FRzk1fVqAP`u3OeUe_Qv*KJ@y>|@Ex+!RIj&M0ow7jjvIlf1${|F0>uJoo!Fjelid&_ zb--%w*H!A?+97UHCxNF8F6`?fQrUbbkhEo}OlN<^&s+ARTxhQ~^W8aO#h!ACTJ1Rx znT|Z#YAYf;dE%?*rUx;bY8!b_18j>2`UcBzYg>_xF&`)bdf@J8L`h1iK{kk<Eoq8n-R@_mrJIbOcd9w{&=CO*Vc zB2P?visn12x&$L{h-%ex+c>5? zrHXzx9&A2!y?R^{iC=|RlpPFY2S7wggQ@)RJlV`O50$fl$&`FAP8CBZ`CKOo90(b^YxqJ&f>xF*l?(S4%;GoFSFtUqZ;$CxDY4ir|91t>E1 zbAN^O(A(GVAuI5XpX|268K+YrP(^n~15u1xhMq^O^FBiT(@wGqHGV<9s5+`;2A|A= zOQ8~Tp}|yi>#7C?r<*E&!nb_l4e0eZz;s9qXoFSPf-)$klStd%JloqN-Kv{t{yOu; z?A+}qXzPm)6O6|djU_WG(S^AE?w8;{j7 z7#Kidj@F4vxRiPVo#k0Tucr2W{*_weXtuut>2`Yn5AMNWV^w`7l_C8Cb_yo5;^;wm z|F|so>EhMyVwX+=`oVHjVdGV*n;;#j3ZME$a=iXWLz|0v4EL%gvW^g$sYq*E{vBKV zMg=_jusszv&r7C{qT_lmpF;09drj281S=<_Xk2Tu>4H^Vzn$KZrHr!^``Yb7E+V`q z^TuoP`h0Ea%;>Z@vL@mCyZy6%bq;37+Mrv7;_kB9X~I_{n1O?Wfy?<%2A<;Ry**Gv zM4?vYJ}oZr->jS%kzq_pC)t`wy2a*=P;s5)f3F)i{8r*N?}SF&8&M$$_id6tf}H-7 ze@|QZ^Dlgo^NmyjB<8)YnN0>}f*O^-m;w=R2#&bEuwRd+^6$ z<46;d#Ok|ut}AMJY(pHH0x6#Ne7foJ$evj0|AU&x$ytFSWuJ)}{>F&?Q8C402k)Vy zlcGNuO^hdYr*qARl~qzJ}=ui`sxb>)GbYv&CDDRY!LXEH?Vp-3%`kuBqJ?k zE5c)!GqS`g%c-_FdYmZuIf-XZev*i6?~eR43gaH?`sG_1L{!1SBQs31VPyHPo?gj_hU=`$4&HkdOueP%~Us(_?D~x80P04y3`a^N=`d2F^ zkPM;f!ZN9sc(RJtc?0e$g$fJ=rf;Q#8rZm611ruVGnRkkt z^SH@Wet2OudsAbR)CPA+_|p7#e~Lq96*+{f8wOi5`UWgY(}}NX0y_*Wup<>y;16@} zm?)(lxpS{@OIHvdR^_3Q0qo(*Nqbaiz6Cg$6{`{!NNY#o3x!tX zH8+Lh%<*gpQzPV54OUi;IuhMWq%7<#6MAM}?T-K`>q<#~A-s_xAx^EgxTt?I3Y3sKK`c-!=6Z{38B&kXM}3fF~&U)~h{ ze0zrD!D>4VOaArLoSmASTO6kSJ-$Tmw5M)l>w|@JE^T5GzDi zm(kldQ=9azgODP|WKQJBU3u(sxfO0?L@bb#11usgV^0@Q!R_oO{~#VgbI@~Jw{*-n zm5J}yP*Vx41vcpDXj&aXxd%eDX;J-uxYz>^M_e#*+3C* zADM4_I7i{$tL=8Y{oHuGw@Rs2OC28tSDALm1 zDh<*g?E+C0q*HPu-AIFgfV4=L(!D{tW5;S!>1^V~%kR z;(Wo^T4rzESq5m1v#P9m8@mP6TRAmyR^>UOAJCzPwx|ghSgN#!??vC%DCZvArywXAwly@^Zn08XG06yKN`6G7#UKvf zhJVlN$rZd`rp(mFzL)3rZe>a<#VTiZ91`O6h?i5nnc!r^r;w&?okeo|I+Qjax2b$( z$xP{f0JA;XGhV9uHvT1ZsxB6)^?~Eu^aGkR&iEQqdTXXV7QI~gpUeEteNt45Wr(*1 z^@BeWbV+CjTCUZ3J|nO2U7`O?~u}Z>+;G(EDf^# zvVMQR4|Gm-lz*Mi``7Xpgs&;q&^@5V+WdMUahWSynKIKznNc0KO(nzpM$Q2QX^Z0O*q#s{<<^c=4$&<3JAA#HgXpJxu&`l zKkLNiO=fR`lgY)23m1q5^)Gj|h1`KI=((3|ZsP^sX1zwjOhTa*ixxkm}7MX3t5cNHUWAYP7Y8(@}c zP`-=X_sytGAKmbf?RCE<=naMjONd`#8?ovZ&c%hR&oHU##4`-amG zKJ^LY!Q1?gK*npel2E}A#E zERxjFz0Wn8atM%}{mRY}oEfj(`EX?9n3<X0~Vl>(v85;rue%mT=8udKr zVj%g=o@Tq=d2fQhY|6DAQtKt9+W6zEcjq7d?~gwz&zq7d2V_AkSQa3Rxgb_XR}Xa?)c85yl~Et6;$tveML1rG^-v1pNGzU@AG5Pz+{1KbvHsF& z!3&gapifVeF*iQ(&}*t3t)hNf6QK?@)Ro6`$Rx9uO)8cX@kBKIeD#x>pxUlB!(Zgq zu)EdnW16tI;9<-!2D?+qpY$S&*XIglLLF&YpM_JnhO@^ST&+zbtlcTpe~3<&CcO20 z?z$wEJ(F`Bql@e8c(I3@y3+#*vu~ndhbx8d*N9&gJ3@puE(4W$@!dDL50|fcK-UlG z!VHFeg@hWnB3$VLs;-$#8BKAH7rQJqFcCO9D2O{JZRBOlIUUW=juts`@{j{~{IiZv zF1wc=#ZYYC)gjK2vS9QB^$mf6kj=2~yI>RX03I$~pAyw>>u@Ad62e-VFFueF z!b-sEnE;~y=H0~c9~Erg$D0TMp$vLM$s3!+HGZB%$d06$SB^Z5ZG|VDynrER3Bkzx z)b1jhlbZi)@+4rhn7M4oysR;Lf!aYsZz(P|a+kH|E@RoVW)zg;1x}RdM=|PIQqhxg zIUQ;61l4d=!3T*edOJ=HZ|F^Z;mNract52ViZ3sGU)YmSncX;A7GI;ry)Zp=XmH^EJ+u2-9PC-L6r}=n}a| zV4R5xg>Rb`AqvE*8EX*EpZ+~jby%tXz)Ns#E^G<*#D3YzO)`=2X_`O?q3JNj;+cu|Fu>HIsPulXMcF%cBgSNa>UW_+Z-yGzn) zU{bYUcl4#EZ8G*!rkLqgIQB0X?ReV5oiil~hlxkKGd5_ThA!RvOeG-)weFSb?*h$H zNE#YD4=e}LLppjrHQKdvMPwwn`$kpx6)Mf4Z*1uRafO?#YrGI6SYADh5fZ!^S;R+@ zBc_|9SnP$f8^i%~+^!4I*_k7j$aY7Fg?%jMg?2`)pq#D@g>I$uTbBtzn(DaX-@9L9 zhYU<@nU}fa-SE^pY4);E1vI~{)5+lW?0bD39z^V3+vXLZDn@%^OuxY^0eI}p2-rTQmh_y3L%0U#Gw3X+dTsr zYRE8^SxkPf*|K+^TE^|5M%q2`OX#UwQ z*H|W}F#l?%vG&{NX?KYFZeDo)#_Juq!`ywo*Ld4&he~Mp6Pk!4|KTFsG*Hb-J~~An zj|s>$*UKw&-H#+m+TRZKwmNDG_}WFqkPKy+n+@>lbl44WM4SMD=-!m+;X%_Gi4Ilb z1C7|sFWgBg3IfgIeDhuhh4~Jp2K?=$*6&?geyc7UTfA+!-$CMOrWLFQ3rt^0PKpOC zUA7GF1n0hA1p?2g*nI8$M8zuG_0DkMpss4YKA1DZCh2*5*AmjezP&-%*-Nvd9AcKX zm(0wzwngEX$4w6hMmYyfT-}Xldf(iu(|HHy44@ z_~bd7bFnT3DEGqVMxtMId}7e1dn=k5^pNpkz}fKc5YcH;E7=y@zM3ba&Z~rC{hu}~ z9MN+@EN}ZB=BoxDJc5>Uv1p-Am7=`wHZAq_s~(EH`Bdm`{hI~9{o2oT5DnpSW#;U*2`affyZ>YkJKF;5gP1 zV?!V#Xn)Jp$%pDMn5dwDIxoJ3WR)S>NBIy!FMOhB95Row-lu-&6ckZ{I6da6l?a7W zX%aJq2@#ujDF_&pURP$!BH~Zj8s3H+;pH|;4)yOdago>I8mrnRX}Y2Dw@4!cO-oFd}8AC zZB5X!6O6iueKgMIA*+zpNY*D6^5d86|-!&XeO-5Iy6B-I|{NAJBURQ(`6R*{zs1Rq%(9S#~q$>}{CVg!8jDBiD zfBVa*W~7tt39Os;<3!tBNVW(TjkOWHXLpBmDsT z7o#h<2N;*GzDpXkyv*N;Uye%Xjs3hfpw<#K203G}UiS|8Q(3#G)8c?t z)zwa%FCfXp-K2}aoEp7?>`dHc91nj_cOCIc-1ho7w94>Hte}K(gA7$R3&dV-6%W7CUU%HB=LM5v3F|#;8iX+9T@7wI=fq(BS|v5 zrN_T31v>cFGN4)Ki0HKjW^?b%fF>Byg(r=ts_SFDYg`&b$-t5B-}k}o!GFsu70rk! z)!Cx&AO;5F|G%01orm0bp$C~6LG5URqfOqL?)_jJ(aQWvf$ITAvNo(>^rz~2-J;4X zywkspxLjuG?B^iWy?+ae=qQBX^E#Z+B5v{z>yzTaw&5c@!2&AE25keyKaJ!d0&@zE zN&Rw9Mw2+!UZB(5zt37DalXLPx}+dqg>8t8es=Ew~z3>jq`>JhqeQ`-J5mbl`U)WMAjH?czx_p7VIjX1J zUs7|XQ2!*2qj~j(1J~BG21BtAKJS8Qm@Teem5zI=9UGPvla^{-vBi;u{I?c1SALuT zftmX6D(6}(*xyjAsB5Sm<)WDxoe)_PVAbn-UdJMi|FC7%J0wZjRX${hq)rGjq^M3= zpiY{{m-nNr2s}}@n&Ossa1a1UaW|k@pfPXPsOs&6uaYBAXrM3vK}KQlCVp=t{K1_5 zgk>BIRz}4j_D-d?^Q*0rvSOz?`R&`2K2bMb#B{}(nt9XGU!g6n-6C;yQpI>GBIayx zf*C7|$b)|>SuQ#h0&$|>{@*)ukB6c{IM`9??jt=51!6esL6PrR{|9Kv8mJk4q1Ah3 zb|^cD+@xsx-M6>B?q8{PmD7TAy6v4ww>J4AshT@3s!v5IE?H;9DQG4LdR&rG&`$&V zEC~rPn?M>iLFi@ZqyRDh%m%r?4)|tYFiI&+9A(`6z<`tpC6fmO@@)>(Up$N5Yr<&5 zcRpA4pB~nO53dV&s8ZqoGcL2BZ{LOysIhbB0vvnJhmIqU>kVZFQyU)=g^9YUBt+It z%#0-?Xq{~`UAav;=h6bR-R7Ms?I6{-M$&@eHwVJp<5VE zE&-e$Si-i!)LkeHj^?|X>OpnqZ3@g!PAFM_qO0_fY1 zAqX{ej5}4uv43vRU%X8RDJUsfb2GSqlz$Q+)lB%3WvmvcX!|@ThChU+h2{;u_K>d7 zn1mnXLBM1sy;tc4Gz3nv{Ge2qcAu`%4Z~3oWOb`){ZoNRVNxds3gQNf#d)Hr$%{wl zi6n(fU^c~oXLheo`7~1W_$CNVpCrXa0nFF&EEiYgr`#q(N;ReC1A_an44ly*H{b`u z2N})Je}{t5SYl6*ebvS~D+qo`T6fCSVgs+2QFNz2cEi&S^XWc?wQ?e}4NZQtPuzQV zFJv%j(89iX6O#B{bTp%QVLHu^ol`kTMxf0lcc&0BaV4TwK+UiP6?9q_^1*TYzSURO zk*mpy1BmJ($x)$S;OzQxVOr{M6y~nZ?7=nQ(+7o_>|jiE-(9vsh`FAOma?Eo(lUj5 zR4DBfl~u-G{@k{WWw_n3S+auouUCvzA(BV$DU8dSajS*1;QZD$SG`K=pg8_!DF%bN zE`HDyvujCk!ato-*5n+69mEOd&z+c|zr;kGn`zVhv=-(Q`!eK*#1@!pgHv||f{^yl zE1I4%j8vkrZ+z|ms(7ADlWnzzIY7--6ms2(jWP00_xhUZ?Im(AR7Ad;4-44QcJKmW++|(cKb#){J%@E5ZY;j!FX;*9Lb#wGw;MT z^aRyE^?Mo8MC$YP#etAT&@#hVL;+xJkjU3py-mAMD$>BYD*GhvH5r&=5xYg3j;#vW zdLQzjt;A2*xFDh89Pmr?g(?V(;4J&6ArYBG>iDk-OMjAns~n3igpR!Xu+3t+>=@=N2t{ z1wo79C=D`7Ma)jz3wrjnbWcSpeu^l_Tsb<$EYn+iU9IpYWew?JyE#x&MrfyU4`F&(|08kVS*rmMu?oQkI8NqQb&-sFXdH>jipbUrKQ1T#b5#3RUHwhr#24bGF91N-CzhIclw*QkzQ-pd?pccC2pR)iwk?cg~ z1uzEq3kKp+CPn;rBK&VUlAbaUDoPK&69NI7{$3DGiAV?}{&P(WmcwN5QD5ImFoXWD z1&gr#1rr8xG$9Xndm-}LTD(D2`IowDKPavlUCWikB1C@;TZY^9=--Pnmu8=jd`-sC z=rxl&XPDe*9M*v5dI^sM_2CHYOMH#oB__WZPsm}9Z5hq*qUVZ}+1$-hW`Ma^<@~bk> zCaewOfBv9x|8WyBIbhs+W%ymH*`;85*(rq{x2g8B@Sg-<(U2dq8y831&-NDKDQR=T zC2%x|jLuUsj;OCLOP@&CAUhT>rQ@spH*ODUy$D~Pm78~Ct6@%Zh>iV_ z)nU^iq*08F;VN`Blh%&oVS_`fLkF?zTgLEK;@Sd_uUtdvL2UM=T-=Mo&XZ3Drr55< z_jAfRhtk)k%iVCFH@5dhcvvu1OsV+tIhw^im3~sPl+L*CFDEo%U=wDuXxNiyuX9^O zaMDvd>QTO1vw4rroQS`R zjnjW+8L}98UiNxbRXnWHOPc*b|IFH{&bv0`WrKAS&Zej>2eSmH&TW!(F}a;IWDtk{ zyJq3|vm!BQMgPUI@807{I$6=VzhFq0anu=}uGgNJQxjU8rKgs2ocvdioxp#tpg3Gufpq;mCfO|azyET{vRq+2! z(*Bb-?a+qXuiOmz?z>X$S{A!&Mm7Ii2~|~sU!r16eHq%adckL8-;;6`fteAyX)Z9f zp6(5_M})-H^Xv&J2?eDQ4yz<`&b}D&;0#Ik%M6E*ieTfqXo3X0m%%%rxB}dO_Q+c? zxtX;|+$BDjap|01ez{-NwkX6r$+?Bcx*C8S01yDSrOxwLB*pREeH9MD%fe&Z!<2fgr3Kry_g+=!I%KEs_pZZh3ElW(DrRC4iuqd(igFhgYPIpD@ez-RRLK$ zkjER1PTVWN3xR$%0ILQkgjzTT#2_fxksGj60m}jH@a9SC1zSTiTpSsC9kZVFzc1yM z?^MNl9Bpy)T+-JH?d>$pe()`9ya_B!H)1zy7ThK}G5@eT`UNKUcBZq(-$SJ+udP<` zA8nkq1Z(Y!Y<`9WPXQJt6ao+ua`5Ib|aMt$_%qyDR@PSU( z5!hq7e~2J-X1v6=p1Q27S7l6;4lfe0G3p}zEn*edSp(d{DnK8)ZPL$MObw2e-c*wJ z*WeSkIxNmxBA2BlwX8#I*tv!_bkgn}+Hl&BO7Xa#sC+}#+ZlNqjEg?jr%78kBYF^Fd{$vFdyC?K>$WVx7rN)vo%pzaqi6QUC<= znP`6a=^6ry5iCA0{}4g;ooQ#@iC4I0ztF^c`$}MQyTyqtW#Bg%iZXh$l(O!1T{-5m zmTgVUll9`A)`6;{xC0@HXd&#<%fUEZ7bl|~UV%QzIO98Nk`Bt&*3eh&>MDDIFBxni zJOjSD#V<3j-yEgwT{f2H4Y?seb!-L;2$w=7<>|NC-3^iobt=-D!(Op2`l^+X<~t^n zRc@|YL8Q&%w75)=R`m(WH!Q9YeEw@L>KT#v7&rdr+}#IMEz})+NP|MW(D($GSiQ|` z?%(>d!(9Z7IBjM75+=x!nT@x}b5s0f@p59*{5`wek<92B*ZUph$VmRkI(X~IE0a-; zJ@ofsPJ4|-XN4>QLSIzWYd@cbxYr%#$>VO;q4!gZJEhlDU-!*@zA&L#OPBc!94s5^ zx=dtEq%7&PjQetq$9H>AHF*=t|hF|1RJSo~!ts)-&uONwV^ zGN}HtmtZ-yueGnUarrbX#fnx#%D}5t@fo`poo#Yv?RV(z^-{%pHdQ`Rhm~~05xwd- zv3C^BzAVSQP@_7l<|_|cn613ZbI{@{MaEFhUnUP%{O^A8KdsLs3Rn**3`{XgYP@LY zX&}2q@5ElvCpo+lwJtf#ls*M?=gQ|oDHhq5`$XAUqw&1_~#c`1lT--SRGzS3rN zB=n+xW+CyA^2zI5!ly%)kspc)Z+pD6&mC~fJ2Hg&BkH7UH;P_i9Ty9bd|km9 z*?re_CguXuIkf5{Isj#^My&gHl!?oogI9qjHgN3^$bub*gPNZVw{2tL)*CVvcHgm{ z=Q4lbgwl0y3B76q=zykx8NZ8JM+m)Y#fbyadOZ^7UJQ>|0*Y>{GCAN>0Jy7i_H(=* zz|Y(Qc7x~qWNqDf04hPE_FmQJ(84o&z`yGP$M~f9F`1Ap>f2jcA)TvNglYw*^FWx# z8;vA5pPq9NdH7E1s_FiWDf+-}_UND~kybov+R2w2z8yahvO84MxWh!d3qp3`j} z?3tjCx9N{SDB3vORefiy=xnTz@n(^Ik)^$=atpk%*6c<=9-XDt3Jf>jhjx|YzX_In!Has<`Jj--@!kCyYmE=a&?7yD zhWalbf3sb4^H90^vssPA+@Dx8X#{#t57)hHmCS1CKc6?8_W%9?u#G$`(8F%-PLhR1pmovy z3x-|?y5^9!UK`J#BRV;g`uZBCjHmz&Su!a^wxwl*=$Afa4>7ZyPv@q{vUYq^Mt{Ox z;s0m+2r=4mFop(%|AHyK z!+qh|&lAu9r~0+fI?zmtIvb?>+&nBjB?SjRz_`X$SE%&#iU~BX2`=?t?U@v*ov42w z1aqg7&|8KtsJS0FOE`VkoQ96r=2Ge5ooz(UUPtWG#>zTo~ z?|dfIDQ~x?y6!~{K*-2;OGK$c+$@A7u@SOb*&5fJHFA8ZCH0Xtn%;bmS9w_&ewVZc&No(KvLY7$!v;BoUuqgTkjn#~d6)8eDg-C`eWwF}k zgzgMU_N2v94_42C7T)MrKXxZ72KS0%9o}eCzl0z1 zE=JO1n6>wh6g0+7dWlzC?fkYG< zg$;8?NJHV6;bnIxNgC7*PgShetje5v!*uD+w~};d9J;&z)6MNzToN?iaJtcu>xPS|jY?2_4vlz2?J@*LlIF%7r*={4f#@to%eRsJ&Uusxc zI9?~=nfCTQ`|O6q`vK7}Q=XT~EZnxvDZftp5Tbhj5mOO4SW0I|TjO-WMIm?c_qBFg zdZl2CBz?R(S;8%;)@5Re<|Qm~i7jvV_zlOhk7kkfKVArvmeR>p@x<_aG}}XZQgc+I z_KM@j%tA##)QDKGzUcBr(m8Ho@u%jfar)Sks#*pe_8-jJZ#ct?6n-{@Q^6Zt`{^Xu z8%0vYLbl5SlA3^)iXI3+F7UpJ=^xPo)%aDq^r|5RSo`WS zh2laIbP4fzAY$N_=n5eJXw+Ok8KdQcs_zZ+&7QO;X*v)9aKZ!!LOiPfROITPRaZ8YPn+7; z+h;^SpFzQx(YlpVJ)twVQLyR;cW2*QXRxCuoYMTTa6Z1hBb0p$P(q$I46+kO}(dzyC{HLrq*F8P?4d1*utP zSAp}7F2p|z@h6E7Nwb)sZNz$h0d?V*|6CRnVidf~r037>9k>8o4#3}Z z2WVh;BbwIf#Qu77_^MUnE$pGO4+^WD3E$tBwA|LLD2VtNa45JTMs=Vy{z^Z_%}QiF zq*PvqP9+R_-IaB4p>%Tc-W|?5v-QVEU8{;ERNk7OH}D0HT3DB<+$kL@IJ2F35|FV+ zg+a!dcW#+FWPH3qd(Bm^lira|Sf}iCoS5NW#THjXG@SxR8aAJ|42+4zGoEat0)Dz0 zKE+onq9e1mz%lXiQ`Lz)u%ebEtlD-?d;;>>>jG$kwwH~L*BZY!;VcGss9$iiJhmpP zEHzgUIe|FnKTN;04J}S#?f6TkewYjzQcc;uWxflVnneJhsHJF*LY2#x#@x0{EID2K z*bfsyRC0s91gxE(@_jzw$Sp5j&|cv%OTDZZ6H=&KWWXC1P2h8;DWyPs_7O3Yr<9%s znY;RTQXdpjn@(?_>+@^uZpA9VgdGitpA%NQ$KD9@M|r%^EGf~@x|q1G9J2r7`F*4C z#9on&5$1Vj;zpQOrjYl8xsZFNDaX&W-Z(79YUqlqKhrDcG3~ijN$adpRHlq4qXwtr zn@PMB{_{E;QNKfNUf0{DS=zU^MOr9GWuQbwFIRl2RXKGXu7&ES@~2HRIZ7`q=%2^+ ziF8V9mCJd(J+!>E{ja6x{v9o$ydu?b#;E+w~E+ zVo&36Y4jn39Y^F|$?8bkFXzxDQWp*=J*GYW`KbdY_)WQSaLGLDMbFmP3}p2Wn(68! zyf5dnyiX6Guzni-+lHFOq84m7@%AC7c;Oe?81lOjkwYX5R5soLbfZ_}e*X5&BYBJWla*KPqm4UQUvs;(I{vuS)juPy%lJe8!;NSaWk^XW zJ?YV)azrj|0WT|=@VYtpwwkDD(1XQ#W z9zgwsbp>h`LyQenxeXK=M<^Vw=423?h*%*jD(TD&3^a(UJ{fJ4@!~m(1G--v;6rBt zOd`$baFx=ye||$PlPZKcblfcnH)J{rSh!_2*xlS~CDHb_k1D{(eI?(At{%!!jcGnN zc%tdT4SfYlZ)}i8%OI+r)NkUj>fdc7%)fROyhWG5R2rV zKYaUh)4GIba~#?~JXd=(Ie^nfWPa1RDn!z_b)BT_*PxN(WC`e~-;U@LYqb3=2CEGH zo&VkzT+cZCd&uI~Nq3+}5`bn~(z{AjZD^fdXr^y+D-|HtgV zWv{Q6NN?5vk4RzckxhPEq|E&lFycR^jA zqk2%|1dvga9Wc51^H}wtZXF&p>YOM+pcG+w57CR zRL?{BPb3=GKccn3w3tyvZ;T97ValHDDhMzteR)K?BQTTHvpi)o{ZjO2(-Oh?Mml~< z^S#y&ih^sl%Q%X6e$x$G`8w}T)wh@yRF_;|n8;0jR~)n`SDY{_`fGDx_Q0x{arO4y z?dCiIz2h3qPJG!4po#NKDz)cojouWmzlHp2#}cn}8gll1!;Ox$*Okl5>4z$khXuV7 zv#AR|7qB;ma6gdy#ACW0wa8@@)?G+i2)XbfF`?}&q-Y4n@&eXT{_5YpywPL9Cfs|Ymh+L~7{fUXGC@p$ zNnqxRlTS_56+90js9@r$?a>_6?~Z$3!aL2M_FX`$=#{<6EKo_~-b&r_)dY-(N;c@p ziE<9Zb~zk-suHfg%524-PWQ!AVt(Gm^n%C^i%8aO!;7CNSZOcALZ-#PN+=Xk=LoBI z3f83)^-{#D;fqnqGUQRpvf+kEH3IsjL%wWQQJr1xL;FBbzhL@BM%l3MQC-C(*2fe6 zC?|r)Xa(7e}wEA?6n-1Bk}NkU*Q$C}h_>Z0TsZo^Qm zXj@XUA4xyo>>23Jb8pl8Z3gU!CEG9MTtKBH}9f5%)0u1 z%0basIZJGmK`~cX{JnJy!b>$)0^n9wF$_dwp!qY$kVSgRZsHCDi8bEM?9jl zd=$41m_LOGCpkyTswkviGOvugJ7xL+MIUPf0@>T;4Eu5voV zW_-`iC%lE^7?ClP8CKk2E`Fmz9TiRsuV;OB@7f1k!zOg_*getsD1Qg7TVJnmw{BS8 z(31fIn5PAg^B~gvqREJN2BVE(ql-$<2Mr$$E0ixfPh_$XP~d&Yh4KN8dr`7o(;Z5V zNLy%=3CvHc{dAI%I61Tegi6G$Au1DaUdTRw9-7urf z)D@)2Tg9Z%?^G7YN*pG{Pei1Thbn9#c`IvEhFs@$sT->JU%UofatqG1AC4ytAb8GX znpj_*X=yf>=cW>?E|@C9{8l42x~t0NRBUgP9rofjwb$EiCk;5I9xz6Wq?5*@QR~XZ z93Kd^IKu*Qg9{l-R|ewwNsl`3`LhmbAQf%W@uTCT{mG*PWnfH9T!I653Q7u))D3kR z3cO3`&UKl)=RYvf_+%xitJyh21B64}E1w-s@Dr7@(}{J}=Oa#U`KkjHYa4)-PvkN> za$<#(*`|QTlc3cWDcWAWiiDn8&7V8ARjk1A7;kV;t%WDip>(tN$yHfvJR6(JN?2uO ztQtXnYhI3KLS8zF;VpC~w3lfMkQH8ORjT3Jn?95sJ)k(hqJJGq_D~5pm@!EcJs{<6 z_Mq-RpqTn|i?^NHZ~2x-iAXkU(L?r2bo^|;v)^C7#PT^Sov`^JyZIkecSQr012z1i zA$6&%=Yenu^0G1oVO8SSjFd>XFPDW2O|udE3(TZL`SY(A^sGF!L!C?35D==%puHwqHSFuIwG|S;cRucQC@2p|oS_!(hi${n$cBO8<8Yjja zP$2CB(F^6Q=~wg}=J%4XM1xSVnSxL(o=`&$+e11sLhU+E={k!S1buw^>%c|1x@Au7 zf=KY28C~CnqEzuk?9=|COvlrU$m2p4e*rG)vBjsWp7%ghylqioI)MEHb?##9WPq-* zX2_ra&YOPb0q2k;!IC?e3pk zhDpxyWZ<-d&NM*eEy%B+n3X7;g4{2UV8r)^>yhd^;RGV#jV9o=SwE#r7kMd^v2KcumqaBou!4c@tYv-@79n;axJ;43Sh5rIt<7WoTi z1-eg)-bmz*PR7rcN^^4Ob$zpKvSJ6aFOsnrOOL1!Um(#>9+z9WM^>_DZe$9V)w8`mH=0Au(x5S_bUs1=JBOEob=p`r^k{TDOD zy76}|TT$vPLO4V}`dcvwFa!krIw@-9924B{de2&V(XktDjCw^NHH}c zpVG=rNz0d(>8fJi?USoL+KrHEa-jum-MF2+%%;gpj{+si+F=@5=_yIlQbbG?u`msd zVPzOath&@C1x1@2f0kYfb*XDLGYA7VGVlY$X{d6-0OrnhKi;2ChKURI04y_X8)0cx z&%FF76C0|v!W9B0Bj=7v4C4JoktZ^hvuC#jOED0J4Vu3>46VKxYT5-L9p{h5?BHZJ ztHo5ivLCT0vp7my1ldobP7Gs`b0@qVN7-tiusp@K;VD$~&c3xYZ;F)w93H)S1vtYX zF0nu3>oQ^Vi9CmotXRa4rjX_>yxMf zQd+30SGmOIWhoK%Bve|o#ey${my&Y%NmI{cn)9Skutl-y+K%8g1?o2dc8c3tL;DJL zj`kW^?MghIt9sDN%Hk%z0(aB+>26UWiwLzYp_8{W)DCkrTJyF{&0W7+Y@WCf)SdY< z5tCzYHPGS`FD`MvJ%c8~=4_g2X+@Oz2;wkCjD z@4vh$3Z=asY8OxN$lDLrks{}%9^jsGJ-+HiCzmVqX+W@Y(0;l8i#)2X(&uediS|og z&g+jod+>D$QTazG`u;Z2G$f(Zq!BykjR@7XMV);aY_b7xl9UuH%nI?Wz28~{>8Y?CxtQHUTN9`}3j$8T615=6C zV`r%{!}upcZB2Z%;kl*DCVD{GJF;z7>7)H6VPlJT@aFf$wF!SA=<|z)&$By`dcbw0 zSMKOaeDmz&5p^HmzRm?3&_?uy9?R`bG%1SeG!NHO{_{5aRjWvcq-_f@SZu@wT6~6t zkFY_mp}r$cKXnX+A;+I6kdL0A+J)mI%0GFDfV?z=g00J<#UGjrJ4tc%KD7*it(NHY zwYJ7g$mKnB<*d0lCzA#vE|GC9;8m^527zJa)MDZu9ty5=_>u{AO1$MW6Bd55c}#(G z_kBuw83tT~{7HvaqC;RWa-n18gMu?S<}DCLffc5*NZVDv0xn|QBR*(c2JA*WA;5dU z4#=PD^*R!EC#5*qM%8ovci=)E8u2UW?c}8B2j-C$6jAF^Pe4o^FPO8{7F{mh33=2fd&x}dW)a!;SW61Nea)lS>h=0C!vMEgWdxQ~@m*ZMB{=M4!xnc4GjzK}gGKtW0tTq-6rHU5QQBr4afCNf`9OPT zZu5jO&f>jjWYJXAY|@(GC39TsE-cc%FTuIeO8j~!LUH-yyRht+k9^xDb*^3l(5hY~ z5v|QRfu#_BGwMn0ovVQi*}UQ#e)EUL?I3cTo)TaHYKW)dic(x!wIS3jnhe1F$YZQ{ z%p}9vi4UAilhIo8XUk6M+mrs3`^+ft#JjR>7;7@KZF(}O8Ao($GtV1WXhJY?i)TjR46aHA@uYBrE zBi+<$oi^zg$tYrJ6k%+vS%BvaIlmADdy)FX^h>LNJq-A|dp0OP>xs2aCxR&E&$(N5 z1tlpGfMsz0^M6=iVD0deXWy`Ay`{)@_)4nKZFH8}g)rvbCP!b(C_~oUBidp%zLRTL zJv)eC##UOevJMrt(NsT4tH;D|MTF8tU;_mCJ)j>mb*5Cy+Qn&LjWU}1aCGN>_RQ=I z7}$mx1&)r;HoPyv2l50{%{HXCv8e$P6|OmsKuO)SE|{GBLp+n?&#A7(wdAt*dc)}_ zwGfAxS8&h+l=uKq0Xtpi$RGfAfhC1}VEOxMe2DA73)PYDBTgVr7;)z>SaV(H^2vDm zgpDNqhbL)D6t5reCA)op915_!yx|VlIJ);$!}agk57yshpwBbc zfIn81fbronx2!6PIO886+ygAp$!aFjLPyC>BGJL8+KR0Bvy-#nO2e=5FIX)y zPAkKo3;Xva%by4Te@MV;=(xEDIV0FrYnqO4>=ndX`g8sBKHL%7Vp-N^W5pjfmH#ev zxo(R{&Ym^IsdpHv%d=r67;@`W5GUNYXn`Ai0hEeWCC}ooF-0%-i)Bn-doE&h)Y)i# z;@D1eKY8BEtxJ-k-npx{<)cDrQhJGQ~Dl;i~;uE>U0hiV5B_^th- zo@3n<$~*pI+6LznAp5Zm6?yaxGobTWZ~_7YpMDNUXapmkx_2^KU8zSpcc6 z7zJ2(iCfTqX5R;#HxEh=FpkeHulVY#$j<9Rf6q`$Ib7ANs_egDJpH)kzo+YH$QBu! z>>RJ?^KwHlU-b;BQ>Cw@!!@ODmN?8=zo&BX(NZ^V{>=Yz$B)?iwV_CcavQ!xfly7{ zz?V;3i*vp#)A`+@nIos|uKa)$GOK*^d; ztTAk3jW_a#hcpSR$a!!e^r)z9cGab{HvI|#OG0kYb`7P<_lLCMsPf@$(-+reHe<8` z+F=qY_3;u4y$)?2kKawyolRA}nD}%A{MqeifT;0eJg6dD3EU}FWE zATDOmf?lrs34*XnOR)XEw9Z*Mzt#Xu@eKSxH*dqUnq_Kuo9qk{(6oP0fmtOGium!W za%w=8IYtmxR~Uc%^@$I)YlLEUk^oqP^MW2x@~Sp!Dj45(DZ7x_OPzknB5_6JWgok! zA}gNtxx{OEZhcJx{KOQbNPwdgV0=LOf^}9e1$YGJ91*iM;5wfSg2(4QPifh%T_X7` z1tJ}_b#tjyp;HyatEqwjAm?jC`E2{Pr)X`SP3op!m;!2Ui@0*Vh-{cOWYw~LI`rpv z%qj`Y6GSS#9Zd5aqjoB?!Pz8tt*NU z0{SLM)J{zJ+>jjaIkd8w`+$}uw^%zP+W)d57{PAPy6(Dx=^o}U*p`ktw0>*d0Q!?q zyPygGh_(F<>@H!Q(Ew*r6>J|(PJ<-ZKbLr(4G=JoD0MvI9`YHnf$nu^`L`V~=X%um z2=Rmh#4vMHGqjjhDcabYKzv_8(R)|G2YWV7ZhN6ZVQ2}6>ID}^%i$K}wxeipTzX;|1GM$hPJh8#fQrCA z6Gxo|ysf7YJBEMqXtA-IX_`+`#a?Os3r5t0t&TXc1aE(;h30^KTRlJ=s4?CEwF=ez zhadq*UEH+l06*dSyOM{TPdr}0+hlx@7y(0QVB+tbZMxCI{0|&Ylpc11mVu_hJne_K zYaJgp=M_#zY)rJFaO8u)$aNMsDTfiT`_FsU1mEmTdO;0UtL?@?T-+&vD2!f=7SZ6>26&gR8a=Z=xiNN)IhT# zX;Bb17N`V3gTjW!!IN)+&>1x-fCyOkf;OKM{TEHR#vhtD|4S1AX!!q=Px#RIXOPc1 zO~SwVl>QG*gvKA5x6uC*_5Tl1nDjCDB;HvPGTvtxLU&GRp2b83T7rY`V>7$+2|W9h z2<@2u`COfAc0UpG(m;p)pPGQ)xlFJ6ZzcT2U&S{njgC46Sby9;UQmO|g&$KZl2!1#dvj1P*(|Go(Gx@u!L4$VLQQy?_g08}0mi2qH( z_QVHG!0E?jX45ljFxH4rf5F1RQ2iF1fD1AQ*?NJ4|%T6O;SzgcQ9(5(hY+o0m~K@09=4@qd!A z-O2uwBk#KlKcIS(CgElqi{Ka>xf}I1HU&D_B$yM-fOC4LikcyQb%M$d5Wl6bjN3Hb z>AwLD{7hix#1n=>22Dl*BFOIl)7906G!;hSO*09T5vCHhElksy^x?{^v};I_gP0;I z207AjhJ}Sz>cCer6zRieN!VJ;3e;qYZeSETYAcCQE3Ktj%T**b(^1*7yKB$BboTdN z&bjBD_c_nAo%deeE%v}#z!uHYi!a}*OBGDD0Xc%_KMt`!p6rMn^vipkef4Qve1asm z&=@*huH3WMY*<8w095POENr?(Y%L0DqNW2Wy1X*+LSaI^kA9MU?TcEP-+hl6Fvcpx zXD_!mPak%;9I#1ib>l~{;~2tcl9G9XT`~zRG?;$IM==2$i~@j;>QdBnJ_xh6u^oK5 z@G8-7e}?kpk`KVF^()IN66OH?OYv^vf_zRbbXAhytzlNYm&}l`AgpW+W@_oMn((&r zHMI1}q_j}0a|R_wP#~ER$;_NdntyG2&RZ|LK~%2*A)atQ&5^2>ZQhjCMfBU;abief z9TD54*_g_RX%d2UPv)&o%`w3b4ziw$+To`J0QM9BewHf>N2xJo1Q6drP3^}D@8A!= zGhf_Z+8V~yX^w*z3VijA+}+uGc;>6@yvijzPIpg>E02Z;7X_{3*i#%mlftq5`qcBw znh<3|N(6K~Mx+X+tm|`nXXnZdtNt3F^QW3r^PO|YNNEN~<5V`$zGKulS7mQY<^^UR z>@2&*xyLHc(h_}wql++}b~5X$Hx;bJ^ceCet(iN^(pX(V5zQgSMz>NL$=`2(vK|l* zy{z%@R2^K*bJ-b^ulsGpac%M9NemSxuE+&!K3QgmD89brxTu!(($QN`2&J1r@U`Y* z(@$X?9qN*ZBFiwwQSf9^j(%Pr9(C%-@JRm(<+M)o9?7n>nJ#}$IWhw3Z( z&qst)HqonN#d4R>{h6un{>H3mb;%NFYy#EZ%%2IXVFH}e6B#Kp(L&_o(X|&q(M#f` zVkHeYamGtL#kTw^l=>8t;dBrjDf>+eF;Z Date: Fri, 6 Dec 2019 10:24:33 -0500 Subject: [PATCH 24/42] Those must be writable to chmod and chown on gatekeeper startup --- .../templates/installer/docker/docker-compose.yaml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/cyphernodeconf_docker/templates/installer/docker/docker-compose.yaml b/cyphernodeconf_docker/templates/installer/docker/docker-compose.yaml index 6127dde..6e73a9d 100644 --- a/cyphernodeconf_docker/templates/installer/docker/docker-compose.yaml +++ b/cyphernodeconf_docker/templates/installer/docker/docker-compose.yaml @@ -193,13 +193,13 @@ services: volumes: - "<%= gatekeeper_datapath %>/certs:/etc/ssl/certs:ro" - "<%= gatekeeper_datapath %>/private:/etc/ssl/private:ro" - - "<%= gatekeeper_datapath %>/keys.properties:/etc/nginx/conf.d/keys.properties:ro" - - "<%= gatekeeper_datapath %>/api.properties:/etc/nginx/conf.d/api.properties:ro" - - "<%= gatekeeper_datapath %>/default.conf:/etc/nginx/conf.d/default.conf:ro" - - "<%= gatekeeper_datapath %>/htpasswd:/etc/nginx/conf.d/status/htpasswd:ro" - - "<%= gatekeeper_datapath %>/installation.json:/etc/nginx/conf.d/s/stats/installation.json:ro" - - "<%= gatekeeper_datapath %>/client.7z:/etc/nginx/conf.d/s/stats/client.7z:ro" - - "<%= gatekeeper_datapath %>/config.7z:/etc/nginx/conf.d/s/stats/config.7z:ro" + - "<%= gatekeeper_datapath %>/keys.properties:/etc/nginx/conf.d/keys.properties" + - "<%= gatekeeper_datapath %>/api.properties:/etc/nginx/conf.d/api.properties" + - "<%= gatekeeper_datapath %>/default.conf:/etc/nginx/conf.d/default.conf" + - "<%= gatekeeper_datapath %>/htpasswd:/etc/nginx/conf.d/status/htpasswd" + - "<%= gatekeeper_datapath %>/installation.json:/etc/nginx/conf.d/s/stats/installation.json" + - "<%= gatekeeper_datapath %>/client.7z:/etc/nginx/conf.d/s/stats/client.7z" + - "<%= gatekeeper_datapath %>/config.7z:/etc/nginx/conf.d/s/stats/config.7z" networks: - cyphernodenet - cyphernodeappsnet From e1354cb06fb59b3822569ba9e3ba77ff290cfc3f Mon Sep 17 00:00:00 2001 From: kexkey Date: Mon, 9 Dec 2019 16:05:52 -0500 Subject: [PATCH 25/42] Broker is now part of cyphernodenet and cyphernodeappsnet --- cyphernodeconf_docker/lib/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cyphernodeconf_docker/lib/app.js b/cyphernodeconf_docker/lib/app.js index e9cb672..dad08ac 100644 --- a/cyphernodeconf_docker/lib/app.js +++ b/cyphernodeconf_docker/lib/app.js @@ -449,7 +449,7 @@ module.exports = class App { name: 'MQ broker', label: 'broker', host: 'broker', - networks: ['cyphernodenet'], + networks: ['cyphernodenet', 'cyphernodeappsnet'], docker: 'eclipse-mosquitto:'+this.config.docker_versions['eclipse-mosquitto'] } From 41fa9702d6205b9dc5d0466bca4630d8924ce5d7 Mon Sep 17 00:00:00 2001 From: SKP Date: Fri, 20 Dec 2019 13:27:50 +0100 Subject: [PATCH 26/42] Cyphernodeconf now exits when config.7z is not present but the recreate option was specified --- cyphernodeconf_docker/lib/app.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cyphernodeconf_docker/lib/app.js b/cyphernodeconf_docker/lib/app.js index dad08ac..2e96123 100644 --- a/cyphernodeconf_docker/lib/app.js +++ b/cyphernodeconf_docker/lib/app.js @@ -149,6 +149,11 @@ module.exports = class App { } ); if( !fs.existsSync(this.destinationPath(configArchiveFileName)) ) { + if( this.sessionData.noWizard ) { + console.log(chalk.bold.red('Unable to run in no wizard mode without a config.7z')+'\n'); + process.exit(); + return; + } let r = {}; process.stdout.write(ansi.clear+ansi.reset); while( !r.password0 || !r.password1 || r.password0 !== r.password1 ) { From bc30df87634d7823c7606dab745016745a9b6be4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 28 Dec 2019 13:47:19 +0000 Subject: [PATCH 27/42] Bump handlebars from 4.1.2 to 4.5.3 in /cyphernodeconf_docker Bumps [handlebars](https://github.com/wycats/handlebars.js) from 4.1.2 to 4.5.3. - [Release notes](https://github.com/wycats/handlebars.js/releases) - [Changelog](https://github.com/wycats/handlebars.js/blob/master/release-notes.md) - [Commits](https://github.com/wycats/handlebars.js/compare/v4.1.2...v4.5.3) Signed-off-by: dependabot[bot] --- cyphernodeconf_docker/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cyphernodeconf_docker/package-lock.json b/cyphernodeconf_docker/package-lock.json index c68e746..e5ee0a4 100644 --- a/cyphernodeconf_docker/package-lock.json +++ b/cyphernodeconf_docker/package-lock.json @@ -2451,9 +2451,9 @@ "dev": true }, "handlebars": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.2.tgz", - "integrity": "sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==", + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.3.tgz", + "integrity": "sha512-3yPecJoJHK/4c6aZhSvxOyG4vJKDshV36VHp0iVCDVh7o9w2vwi3NSnL2MMPj3YdduqaBcu7cGbggJQM0br9xA==", "dev": true, "requires": { "neo-async": "^2.6.0", From 2a8cad3dbf120de3938f861ef7e75bfbcf068020 Mon Sep 17 00:00:00 2001 From: gus-t460 Date: Tue, 3 Dec 2019 15:17:55 -0500 Subject: [PATCH 28/42] initial --- api_auth_docker/api-sample.properties | 2 ++ .../templates/gatekeeper/api.properties | 2 ++ proxy_docker/app/script/getactivewatches.sh | 33 +++++++++++++++++++ proxy_docker/app/script/requesthandler.sh | 12 +++++++ 4 files changed, 49 insertions(+) diff --git a/api_auth_docker/api-sample.properties b/api_auth_docker/api-sample.properties index 4d59d81..8fce8fc 100644 --- a/api_auth_docker/api-sample.properties +++ b/api_auth_docker/api-sample.properties @@ -18,6 +18,8 @@ action_getactivewatchesbylabel=watcher action_getactivexpubwatches=watcher action_watchtxid=watcher action_getactivewatches=watcher +action_get_txns_by_watchlabel=watcher +action_get_unused_addresses_by_watchlabel=watcher action_getbestblockhash=watcher action_getbestblockinfo=watcher action_getblockinfo=watcher diff --git a/cyphernodeconf_docker/templates/gatekeeper/api.properties b/cyphernodeconf_docker/templates/gatekeeper/api.properties index 2987271..f037851 100644 --- a/cyphernodeconf_docker/templates/gatekeeper/api.properties +++ b/cyphernodeconf_docker/templates/gatekeeper/api.properties @@ -19,6 +19,8 @@ action_unwatchxpubbylabel=watcher action_getactivewatchesbyxpub=watcher action_getactivewatchesbylabel=watcher action_getactivexpubwatches=watcher +action_get_txns_by_watchlabel=watcher +action_get_unused_addresses_by_watchlabel=watcher action_watchtxid=watcher action_getactivewatches=watcher action_getbestblockhash=watcher diff --git a/proxy_docker/app/script/getactivewatches.sh b/proxy_docker/app/script/getactivewatches.sh index e639945..e19c784 100644 --- a/proxy_docker/app/script/getactivewatches.sh +++ b/proxy_docker/app/script/getactivewatches.sh @@ -3,6 +3,39 @@ . ./trace.sh . ./sql.sh +get_txns_by_watchlabel(){ + trace "Entering get_txns_by_watchlabel()..." + local label_txns + $sql = "SELECT w32.label, w.address, wtxn.txn_id, wtxn.v_out, wtxn.amount FROM watching_by_pub32 as w32 INNER JOIN watching ON w32.id = w.watching_by_pub32_id INNER JOIN watching_tx as wtxn ON w.id = wtxn.watching_id WHERE w32.label={$1}" + $label_txns = $(sql "$sql") + returncode=$? + trace_rc ${returncode} + $label_txns_json = jq -Rsn ' + {"label_txns": + [inputs + | . / "\n" + | (.[] | select(length > 0) | . / "|") as $input + | {"label": $input[0], "address": $input[1], "txn_id": "$input[2], "v_out": $input[3], "amount" : $input[4]}]} + ' <$($label_txns) + return $label_txns_json +} +get_unused_addresses_by_watchlabel(){ + trace "Entering get_unused_addresses_by_watchlabel()..." + local label_txns + $sql = "SELECT w.watching_by_pub32_id, w.pub32_index, w.address FROM watching as w WHERE w.id NOT IN (SELECT watching_id FROM watching_tx) WHERE w.label=${1}" + $label_txns = $(sql "$sql") + returncode=$? + trace_rc ${returncode} + $label_txns_json = jq -Rsn ' + {"label_unused_addresses": + [inputs + | . / "\n" + | (.[] | select(length > 0) | . / "|") as $input + | {"pub32_watch_id": $input[0], "address_pub32_index": $input[1], "address": "$input[2] }]} + ' <$($label_txns) + return $label_txns_json +} + getactivewatches() { trace "Entering getactivewatches()..." diff --git a/proxy_docker/app/script/requesthandler.sh b/proxy_docker/app/script/requesthandler.sh index c40146b..3b4ad20 100644 --- a/proxy_docker/app/script/requesthandler.sh +++ b/proxy_docker/app/script/requesthandler.sh @@ -162,6 +162,18 @@ main() { response_to_client "${response}" ${?} break ;; + get_txns_by_watchlabel) + # curl (GET) 192.168.111.152:8080/get_txns_by_watchlabel + response=$(get_txns_by_watchlabel $(echo "${line}" | cut -d ' ' -f2 | cut -d '/' -f3)) + response_to_client "${response}" ${?} + break + ;; + get_unused_addresses_by_watchlabel) + # curl (GET) 192.168.111.152:8080/get_unused_addresses_by_watchlabel + response=$(get_unused_addresses_by_watchlabel $(echo "${line}" | cut -d ' ' -f2 | cut -d '/' -f3)) + response_to_client "${response}" ${?} + break + ;; conf) # curl (GET) 192.168.111.152:8080/conf/b081ca7724386f549cf0c16f71db6affeb52ff7a0d9b606fb2e5c43faffd3387 From 24f5b552b4d45414b9d61701958686f93c4cb2c3 Mon Sep 17 00:00:00 2001 From: gus-t460 Date: Tue, 3 Dec 2019 15:40:23 -0500 Subject: [PATCH 29/42] Fix queries --- proxy_docker/app/script/getactivewatches.sh | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/proxy_docker/app/script/getactivewatches.sh b/proxy_docker/app/script/getactivewatches.sh index e19c784..7348732 100644 --- a/proxy_docker/app/script/getactivewatches.sh +++ b/proxy_docker/app/script/getactivewatches.sh @@ -6,7 +6,14 @@ get_txns_by_watchlabel(){ trace "Entering get_txns_by_watchlabel()..." local label_txns - $sql = "SELECT w32.label, w.address, wtxn.txn_id, wtxn.v_out, wtxn.amount FROM watching_by_pub32 as w32 INNER JOIN watching ON w32.id = w.watching_by_pub32_id INNER JOIN watching_tx as wtxn ON w.id = wtxn.watching_id WHERE w32.label={$1}" + $sql=< 0) | . / "|") as $input - | {"label": $input[0], "address": $input[1], "txn_id": "$input[2], "v_out": $input[3], "amount" : $input[4]}]} + | {"label": $input[0], "address": $input[1], "txid": "$input[2], "confirmations": $input[3], "blockheight" : $input[4], "v_out":$input[5], "amount": $input[6]}]} ' <$($label_txns) return $label_txns_json } From f1f1de3c3a035eae64b3342d3228f1a21f75a795 Mon Sep 17 00:00:00 2001 From: gus-t460 Date: Thu, 5 Dec 2019 16:29:39 -0500 Subject: [PATCH 30/42] Bug fixes.. unused address test pass --- proxy_docker/app/script/getactivewatches.sh | 55 +++++++++++++-------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/proxy_docker/app/script/getactivewatches.sh b/proxy_docker/app/script/getactivewatches.sh index 7348732..d9dba1b 100644 --- a/proxy_docker/app/script/getactivewatches.sh +++ b/proxy_docker/app/script/getactivewatches.sh @@ -6,43 +6,56 @@ get_txns_by_watchlabel(){ trace "Entering get_txns_by_watchlabel()..." local label_txns - $sql=< 0) | . / "|") as $input - | {"label": $input[0], "address": $input[1], "txid": "$input[2], "confirmations": $input[3], "blockheight" : $input[4], "v_out":$input[5], "amount": $input[6]}]} - ' <$($label_txns) - return $label_txns_json + | {"label": $input[0], "address": $input[1], "txid": "$input[2], "confirmations": $input[3], "blockheight" : $input[4], "v_out":$input[5], "amount": $input[6]} + ] + }') + echo "$label_txns_json" + return ${returncode} } get_unused_addresses_by_watchlabel(){ - trace "Entering get_unused_addresses_by_watchlabel()..." - local label_txns - $sql = "SELECT w.watching_by_pub32_id, w.pub32_index, w.address FROM watching as w WHERE w.id NOT IN (SELECT watching_id FROM watching_tx) WHERE w.label=${1}" - $label_txns = $(sql "$sql") + trace "Entering get_unused_addresses_by_watchlabel()... $1" + local label_unused_addrs + query=$(cat <<-HERE + SELECT w32.id, w32.label, w32.pub32, w.pub32_index, w.address + FROM watching as w + INNER JOIN watching_by_pub32 AS w32 ON w.watching_by_pub32_id = w32.id + WHERE w.id NOT IN ( + SELECT watching_id FROM watching_tx + ) + AND w32.label="$1" +HERE + ) + label_unused_addrs=$(sql "$query") returncode=$? trace_rc ${returncode} - $label_txns_json = jq -Rsn ' + label_unused_addrs_json=$(echo "$label_unused_addrs" | jq -Rcsn ' {"label_unused_addresses": [inputs | . / "\n" | (.[] | select(length > 0) | . / "|") as $input - | {"pub32_watch_id": $input[0], "address_pub32_index": $input[1], "address": "$input[2] }]} - ' <$($label_txns) - return $label_txns_json + | {"pub32_watch_id": $input[0], "pub32_label": $input[1], "pub32" : $input[2], "address_pub32_index": $input[3], "address": $input[4] + ] + } + ') + echo "$label_unused_addrs_json" + return ${returncode} } - getactivewatches() { trace "Entering getactivewatches()..." From 27e2817a61de83f04a23bf96441426f7ef1aa113 Mon Sep 17 00:00:00 2001 From: gus-t460 Date: Fri, 6 Dec 2019 13:32:53 -0500 Subject: [PATCH 31/42] Both api endpoints pass test on regtest --- proxy_docker/app/script/getactivewatches.sh | 30 +++++++++++---------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/proxy_docker/app/script/getactivewatches.sh b/proxy_docker/app/script/getactivewatches.sh index d9dba1b..7b0a0bb 100644 --- a/proxy_docker/app/script/getactivewatches.sh +++ b/proxy_docker/app/script/getactivewatches.sh @@ -4,41 +4,43 @@ . ./sql.sh get_txns_by_watchlabel(){ - trace "Entering get_txns_by_watchlabel()..." + trace "Entering get_txns_by_watchlabel() for label ${1}..." local label_txns - $query=< 0) | . / "|") as $input - | {"label": $input[0], "address": $input[1], "txid": "$input[2], "confirmations": $input[3], "blockheight" : $input[4], "v_out":$input[5], "amount": $input[6]} + | {"label": $input[0], "address": $input[1], "txid": $input[2], "confirmations": $input[3], "blockheight": $input[4], "v_out": $input[5], "amount": $input[6]} ] - }') + } + ') echo "$label_txns_json" return ${returncode} } get_unused_addresses_by_watchlabel(){ - trace "Entering get_unused_addresses_by_watchlabel()... $1" + trace "Entering get_unused_addresses_by_watchlabel() for label ${1}..." local label_unused_addrs query=$(cat <<-HERE SELECT w32.id, w32.label, w32.pub32, w.pub32_index, w.address FROM watching as w INNER JOIN watching_by_pub32 AS w32 ON w.watching_by_pub32_id = w32.id - WHERE w.id NOT IN ( + WHERE w32.label="$1" + AND w.id NOT IN ( SELECT watching_id FROM watching_tx ) - AND w32.label="$1" HERE ) label_unused_addrs=$(sql "$query") @@ -49,7 +51,7 @@ HERE [inputs | . / "\n" | (.[] | select(length > 0) | . / "|") as $input - | {"pub32_watch_id": $input[0], "pub32_label": $input[1], "pub32" : $input[2], "address_pub32_index": $input[3], "address": $input[4] + | {"pub32_watch_id": $input[0], "pub32_label": $input[1], "pub32" : $input[2], "address_pub32_index": $input[3], "address": $input[4]} ] } ') From e8306f8a8ae3750ae58acced1ee3e2186576af63 Mon Sep 17 00:00:00 2001 From: g-homebase Date: Thu, 12 Dec 2019 19:39:13 -0500 Subject: [PATCH 32/42] Fix issue #153 --- proxy_docker/app/script/confirmation.sh | 6 ++++-- proxy_docker/app/script/getactivewatches.sh | 5 +++-- proxy_docker/app/script/walletoperations.sh | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/proxy_docker/app/script/confirmation.sh b/proxy_docker/app/script/confirmation.sh index 3fbfd8a..d0dd606 100644 --- a/proxy_docker/app/script/confirmation.sh +++ b/proxy_docker/app/script/confirmation.sh @@ -30,7 +30,9 @@ confirmation() { local returncode local txid=${1} local tx_details - tx_details=$(get_transaction ${txid}) + # We need make sure to discard error results generated by get_transction tryin to find a watching wallet's `txn in + # spender wallet first,return code will tell if there's an actaul error. FIXME Add issue here + tx_details=$(get_transaction ${txid} | jq '. | select(.result != null)') returncode=$? trace_rc ${returncode} trace "[confirmation] tx_details=${tx_details}" @@ -68,7 +70,7 @@ confirmation() { local tx=$(sql "SELECT id FROM tx WHERE txid=\"${txid}\"") local id_inserted local tx_raw_details=$(get_rawtransaction ${txid}) - local tx_nb_conf=$(echo "${tx_details}" | jq '.result.confirmations') + local tx_nb_conf=$(echo "${tx_details}" | jq -r '.result.confirmations // 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 diff --git a/proxy_docker/app/script/getactivewatches.sh b/proxy_docker/app/script/getactivewatches.sh index 7b0a0bb..b62ca04 100644 --- a/proxy_docker/app/script/getactivewatches.sh +++ b/proxy_docker/app/script/getactivewatches.sh @@ -38,9 +38,10 @@ get_unused_addresses_by_watchlabel(){ FROM watching as w INNER JOIN watching_by_pub32 AS w32 ON w.watching_by_pub32_id = w32.id WHERE w32.label="$1" - AND w.id NOT IN ( - SELECT watching_id FROM watching_tx + AND NOT EXISTS ( + SELECT 1 FROM watching_tx WHERE watching_id = w.id ) + ORDER BY w.pub32_index ASC HERE ) label_unused_addrs=$(sql "$query") diff --git a/proxy_docker/app/script/walletoperations.sh b/proxy_docker/app/script/walletoperations.sh index 404a7b8..0fd45be 100644 --- a/proxy_docker/app/script/walletoperations.sh +++ b/proxy_docker/app/script/walletoperations.sh @@ -197,7 +197,7 @@ getbalancebyxpub() { echo "${data}" - return ${returncode} + return "${returncode}" } getnewaddress() { From 29704c20e57e0a79fc45c1dc7f435e83e60a3243 Mon Sep 17 00:00:00 2001 From: g-homebase Date: Fri, 13 Dec 2019 12:13:18 -0500 Subject: [PATCH 33/42] Add bonus gettxnslist/count/skip endpoint for spender wallet :) --- api_auth_docker/api-sample.properties | 1 + .../templates/gatekeeper/api.properties | 1 + proxy_docker/app/script/confirmation.sh | 2 +- proxy_docker/app/script/requesthandler.sh | 7 +++++ proxy_docker/app/script/walletoperations.sh | 26 +++++++++++++++++++ 5 files changed, 36 insertions(+), 1 deletion(-) diff --git a/api_auth_docker/api-sample.properties b/api_auth_docker/api-sample.properties index 8fce8fc..95e3596 100644 --- a/api_auth_docker/api-sample.properties +++ b/api_auth_docker/api-sample.properties @@ -30,6 +30,7 @@ action_ln_getconnectionstring=watcher action_ln_decodebolt11=watcher # Spender can do what the watcher can do, plus: +action_getxnslist=spender action_getbalance=spender action_getbalancebyxpub=spender action_getbalancebyxpublabel=spender diff --git a/cyphernodeconf_docker/templates/gatekeeper/api.properties b/cyphernodeconf_docker/templates/gatekeeper/api.properties index f037851..335bae9 100644 --- a/cyphernodeconf_docker/templates/gatekeeper/api.properties +++ b/cyphernodeconf_docker/templates/gatekeeper/api.properties @@ -35,6 +35,7 @@ action_ln_getconnectionstring=watcher action_ln_decodebolt11=watcher # Spender can do what the watcher can do, plus: +action_gettxnslist=spender action_getbalance=spender action_getbalancebyxpub=spender action_getbalancebyxpublabel=spender diff --git a/proxy_docker/app/script/confirmation.sh b/proxy_docker/app/script/confirmation.sh index d0dd606..e2d8c54 100644 --- a/proxy_docker/app/script/confirmation.sh +++ b/proxy_docker/app/script/confirmation.sh @@ -31,7 +31,7 @@ confirmation() { local txid=${1} local tx_details # We need make sure to discard error results generated by get_transction tryin to find a watching wallet's `txn in - # spender wallet first,return code will tell if there's an actaul error. FIXME Add issue here + # spender wallet first,return code will tell if there's an actaul error. See #153 tx_details=$(get_transaction ${txid} | jq '. | select(.result != null)') returncode=$? trace_rc ${returncode} diff --git a/proxy_docker/app/script/requesthandler.sh b/proxy_docker/app/script/requesthandler.sh index 3b4ad20..25bf3bb 100644 --- a/proxy_docker/app/script/requesthandler.sh +++ b/proxy_docker/app/script/requesthandler.sh @@ -238,6 +238,13 @@ main() { response_to_client "${response}" ${?} break ;; + gettxnslist) + # curl (GET) http://192.168.111.152:8080/gettxnslist/20/10 + + response=$(gettxnslist $(echo "${line}" | cut -d ' ' -f2 | cut -d '/' -f3) $(echo "${line}" | cut -d ' ' -f2 | cut -d '/' -f4)) + response_to_client "${response}" ${?} + break + ;; getbalance) # curl (GET) http://192.168.111.152:8080/getbalance diff --git a/proxy_docker/app/script/walletoperations.sh b/proxy_docker/app/script/walletoperations.sh index 0fd45be..27a7711 100644 --- a/proxy_docker/app/script/walletoperations.sh +++ b/proxy_docker/app/script/walletoperations.sh @@ -123,6 +123,32 @@ bumpfee() { return ${returncode} } +gettxnslist() { + trace "Entering gettxnslist()... with count: $1 , skip: $2" + local count="$1" + local skip="$2" + local response + local data="{\"method\":\"listtransactions\",\"params\":[\"*\",${count:-10},${skip:-0}]}" + response=$(send_to_spender_node "${data}") + local returncode=$? + trace_rc ${returncode} + trace "[gettxnlist] response=${response}" + + if [ "${returncode}" -eq 0 ]; then + local txns=$(echo ${response} | jq -rc ".result") + trace "[gettxnlist] txns=${txns}" + + data="{\"txns\":${txns}}" + else + trace "[gettxnlist] Coudn't get txns!" + data="" + fi + + trace "[gettransactions] responding=${data}" + echo "${data}" + + return ${returncode} +} getbalance() { trace "Entering getbalance()..." From fb435655507980755499634a8227978e8555c6f1 Mon Sep 17 00:00:00 2001 From: g-homebase Date: Tue, 17 Dec 2019 11:03:30 -0500 Subject: [PATCH 34/42] Add more fields to gettxns call and limits --- proxy_docker/app/script/getactivewatches.sh | 6 ++++-- proxy_docker/app/script/requesthandler.sh | 8 ++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/proxy_docker/app/script/getactivewatches.sh b/proxy_docker/app/script/getactivewatches.sh index b62ca04..d0323cd 100644 --- a/proxy_docker/app/script/getactivewatches.sh +++ b/proxy_docker/app/script/getactivewatches.sh @@ -7,12 +7,13 @@ get_txns_by_watchlabel(){ trace "Entering get_txns_by_watchlabel() for label ${1}..." local label_txns query=$(cat <<-HERE - SELECT w32.label, w.address, tx.txid, tx.confirmations,tx.blockheight, wtxn.vout, wtxn.amount + SELECT w32.label, w.address, tx.txid, tx.confirmations,tx.blockheight, wtxn.vout, wtxn.amount, tx.blockhash, tx.blocktime, tx.timereceived FROM watching_by_pub32 as w32 INNER JOIN watching AS w ON w32.id = w.watching_by_pub32_id INNER JOIN watching_tx AS wtxn ON w.id = wtxn.watching_id INNER JOIN tx AS tx ON wtxn.tx_id = tx.id WHERE w32.label="$1" + LIMIT 0,${2-10} HERE ) label_txns=$(sql "$query") @@ -23,7 +24,7 @@ HERE [inputs | . / "\n" | (.[] | select(length > 0) | . / "|") as $input - | {"label": $input[0], "address": $input[1], "txid": $input[2], "confirmations": $input[3], "blockheight": $input[4], "v_out": $input[5], "amount": $input[6]} + | {"label": $input[0], "address": $input[1], "txid": $input[2], "confirmations": $input[3], "blockheight": $input[4], "v_out": $input[5], "amount": $input[6], "blockhash": $input[7], "blocktime": $input[8], "timereceived": $input[9]} ] } ') @@ -41,6 +42,7 @@ get_unused_addresses_by_watchlabel(){ AND NOT EXISTS ( SELECT 1 FROM watching_tx WHERE watching_id = w.id ) + LIMIT 0,${2-10} ORDER BY w.pub32_index ASC HERE ) diff --git a/proxy_docker/app/script/requesthandler.sh b/proxy_docker/app/script/requesthandler.sh index 25bf3bb..e537434 100644 --- a/proxy_docker/app/script/requesthandler.sh +++ b/proxy_docker/app/script/requesthandler.sh @@ -163,14 +163,14 @@ main() { break ;; get_txns_by_watchlabel) - # curl (GET) 192.168.111.152:8080/get_txns_by_watchlabel - response=$(get_txns_by_watchlabel $(echo "${line}" | cut -d ' ' -f2 | cut -d '/' -f3)) + # curl (GET) 192.168.111.152:8080/get_txns_by_watchlabel/