mirror of
https://github.com/aljazceru/cyphernode.git
synced 2025-12-17 04:35:14 +01:00
First push from Satoshi Portal's own cyphernode
This commit is contained in:
96
CONTRIBUTING.md
Normal file
96
CONTRIBUTING.md
Normal file
@@ -0,0 +1,96 @@
|
||||
# Contributions are welcome!
|
||||
|
||||
There are a lot to contribute. Bugfixes, improvements, new features, documentation, tests, etc. Let's be a team!
|
||||
|
||||
# How to contribute
|
||||
|
||||
## Cyphernode Architecture
|
||||
|
||||
Cyphernode is an assembly of Docker containers being called by a request dispatcher.
|
||||
|
||||
- The request dispatcher (requesthandler.sh) is the HTTP entry point.
|
||||
- The request dispatcher is stateful: it keeps some data to be more effective on next calls.
|
||||
- The request dispatcher is where Cyphernode scales with new features: add your switch, dispatch requests to your stuff.
|
||||
|
||||
We are trying to construct each container so that it can be used separately, as a standalone reusable component.
|
||||
|
||||
Important to us:
|
||||
|
||||
- Be as optimized as possible, using Alpine when possible and having the smallest Docker image size possible
|
||||
- Reuse existing software: built-in shell commands, well-established pieces of software, etc.
|
||||
- Use open-source software
|
||||
- Don't reinvent the wheel
|
||||
- Expose the less possible surface
|
||||
|
||||
## Center element: proxy_docker
|
||||
|
||||
The proxy_docker is the container receiving and dispatching calls from clients. When adding a feature to Cyphernode, it is the first part to be modified to integrate the new feature.
|
||||
|
||||
### proxy_docker/app/script/requesthandler.sh
|
||||
|
||||
You will find in there the switch statement used to dispatch the requests. Just add a case block with your command, using other cases as examples for POST or GET requests.
|
||||
|
||||
### proxy_docker/app/config
|
||||
|
||||
You will find there config files. config.properties should be used to centralize configs. spender and watcher properties are used to obfuscate credentials on curl calls.
|
||||
|
||||
### proxy_docker/app/data
|
||||
|
||||
watching.sql contains the data model. Called "watching" because in the beginning of the project, it was only used for watching addresses. Now could be used to index the blockchain (build an explorer) and add more features.
|
||||
|
||||
## cron_docker
|
||||
|
||||
If you have jobs to be scheduled, use this container. Just add an executable and add it to the crontab.
|
||||
|
||||
Currently used to make sure callbacks have been called for missed transactions.
|
||||
|
||||
## docker-compose
|
||||
|
||||
Deployment flexibility:
|
||||
- distribute your components all over the world using docker-compose.yml constraints in a swarm
|
||||
- scales your system with load-balancers
|
||||
- automatically restarts a component if it crashes
|
||||
|
||||
# What to contribute
|
||||
|
||||
## TODO
|
||||
|
||||
- Stress/load tests
|
||||
- How does netcat behave on high traffic
|
||||
- sqlite3 tweaks for dealing with threaded calls
|
||||
- Security check
|
||||
- Installation scripts:
|
||||
- Configuration (config files)
|
||||
- Deployment (docker-compose)
|
||||
- Launcher app with lunanode
|
||||
|
||||
## Improvements
|
||||
|
||||
- wget is included in Alpine, we could use it instead of curl if we don't have to POST over HTTPS
|
||||
- Make sure everything is thread-safe
|
||||
- There's currently a flock in do_callbacks, do we need one elsewhere?
|
||||
- Using inter-containers direct calls (through docker.sock?) instead of HTTP?
|
||||
- Possibility to automate additions of new endpoints?
|
||||
- With name, type (GET/POST), function name and script file, it's possible
|
||||
- Autoconfig pruned property (in config.properties) by using getblockchaininfo RPC call
|
||||
- Compile lightning-cli during configuration process (if not too slow) instead of precompile it
|
||||
- Add results of gettransaction RPC calls in the DB (like already done for getrawtransaction results) and check DB before calling the node
|
||||
- only when confirmations > 0 in DB
|
||||
|
||||
## Features
|
||||
|
||||
- Timestamping (OTS)
|
||||
- Electrum Server features
|
||||
- Web Admin Panel
|
||||
- Using Trezor/Coldcard for authentication/signing
|
||||
- Multi-authorization: SuperAdmin, Accounting (transactions, etc.), Marketing (usage, etc.), etc.
|
||||
- Web Wallet
|
||||
- Using Trezor/Coldcard for authentication/signing
|
||||
- Index the blockchain
|
||||
- PGP features: signing, verifying, etc.
|
||||
- Full blockchain explorer
|
||||
- e-mail notifications when things happen (monitoring):
|
||||
- less than X BTC in the spending wallet
|
||||
- more than Y% CPU used for more than Z minutes on the proxy container
|
||||
- bitcoind instance unreachable
|
||||
- any error
|
||||
11
cron_docker/Dockerfile
Normal file
11
cron_docker/Dockerfile
Normal file
@@ -0,0 +1,11 @@
|
||||
FROM alpine
|
||||
|
||||
RUN apk add --update --no-cache \
|
||||
curl
|
||||
|
||||
COPY callbacks_cron /etc/periodic/15min/callbacks_cron
|
||||
|
||||
RUN chmod +x /etc/periodic/15min/callbacks_cron
|
||||
|
||||
ENTRYPOINT ["crond"]
|
||||
CMD ["-f"]
|
||||
13
cron_docker/README.md
Normal file
13
cron_docker/README.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Cyphernode CRON container
|
||||
|
||||
## Configure your container by modifying `env.properties` file
|
||||
|
||||
```properties
|
||||
PROXY_URL=cyphernode:8888/executecallbacks
|
||||
```
|
||||
|
||||
## Building docker image
|
||||
|
||||
```shell
|
||||
docker build -t proxycronimg .
|
||||
```
|
||||
3
cron_docker/callbacks_cron
Normal file
3
cron_docker/callbacks_cron
Normal file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
curl ${PROXY_URL}
|
||||
1
cron_docker/env.properties
Normal file
1
cron_docker/env.properties
Normal file
@@ -0,0 +1 @@
|
||||
PROXY_URL=cyphernode:8888/executecallbacks
|
||||
515
doc/API.md
Normal file
515
doc/API.md
Normal file
@@ -0,0 +1,515 @@
|
||||
# Cyphernode
|
||||
|
||||
## Current API
|
||||
|
||||
### Watch a Bitcoin Address (called by application)
|
||||
|
||||
Inserts the address and callbacks in the DB and imports the address to the Watching wallet.
|
||||
|
||||
```http
|
||||
POST http://cyphernode:8888/watch
|
||||
with body...
|
||||
{"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","unconfirmedCallbackURL":"192.168.111.233:1111/callback0conf","confirmedCallbackURL":"192.168.111.233:1111/callback1conf"}
|
||||
```
|
||||
|
||||
Proxy response:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "291",
|
||||
"event": "watch",
|
||||
"imported": "1",
|
||||
"inserted": "1",
|
||||
"address": "2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp",
|
||||
"unconfirmedCallbackURL": "192.168.133.233:1111/callback0conf",
|
||||
"confirmedCallbackURL": "192.168.133.233:1111/callback1conf",
|
||||
"estimatesmartfee2blocks": "0.000010",
|
||||
"estimatesmartfee6blocks": "0.000010",
|
||||
"estimatesmartfee36blocks": "0.000010",
|
||||
"estimatesmartfee144blocks": "0.000010"
|
||||
}
|
||||
```
|
||||
|
||||
### Un-watch a previously watched Bitcoin Address (called by application)
|
||||
|
||||
Updates the watched address row in DB so that callbacks won't be called on tx confirmations for that address.
|
||||
|
||||
```http
|
||||
GET http://cyphernode:8888/unwatch/2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp
|
||||
```
|
||||
|
||||
Proxy response:
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "unwatch",
|
||||
"address": "2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp"
|
||||
}
|
||||
```
|
||||
|
||||
### Get a list of Bitcoin addresses being watched (called by application)
|
||||
|
||||
Returns the list of currently watched addresses and callback information.
|
||||
|
||||
```http
|
||||
GET http://cyphernode:8888/getactivewatches
|
||||
```
|
||||
|
||||
Proxy response:
|
||||
|
||||
```json
|
||||
{
|
||||
"watches": [
|
||||
{
|
||||
"id":"291",
|
||||
"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp",
|
||||
"imported":"1",
|
||||
"unconfirmedCallbackURL":"192.168.133.233:1111/callback0conf",
|
||||
"confirmedCallbackURL":"192.168.133.233:1111/callback1conf",
|
||||
"watching_since":"2018-09-06 21:14:03"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Confirm a Transaction on Watched Address (called by Bitcoin node on transaction confirmations)
|
||||
|
||||
Confirms a transaction on an imported address. The Watching Bitcoin node will notify Cyphernode (thanks to walletnotify in bitcoin.conf) by calling this endpoint with txid when a tx is new or updated on an address. If address is still being watched (flag in DB), the corresponding callbacks will be called.
|
||||
|
||||
```http
|
||||
GET http://cyphernode:8888/conf/b081ca7724386f549cf0c16f71db6affeb52ff7a0d9b606fb2e5c43faffd3387
|
||||
```
|
||||
|
||||
Proxy response:
|
||||
|
||||
```json
|
||||
{
|
||||
"result":"confirmed"
|
||||
}
|
||||
```
|
||||
|
||||
### Get the Best Block Hash (called by application)
|
||||
|
||||
Returns the best block hash of the watching Bitcoin node.
|
||||
|
||||
```http
|
||||
GET http://cyphernode:8888/getbestblockhash
|
||||
```
|
||||
|
||||
Proxy response:
|
||||
|
||||
```json
|
||||
{
|
||||
"result":"00000000000000262588c21afbf9e1da151daf10b11215d501271163f26ea74a",
|
||||
"error":null,
|
||||
"id":null
|
||||
}
|
||||
```
|
||||
|
||||
### Get Block Info (called by application)
|
||||
|
||||
Returns block info for the supplied block hash.
|
||||
|
||||
```http
|
||||
GET http://cyphernode:8888/getblockinfo/000000006f82a384c208ecfa04d05beea02d420f3f398ddda5c7f900de5718ea
|
||||
```
|
||||
|
||||
Proxy response:
|
||||
|
||||
```json
|
||||
{
|
||||
"result":
|
||||
{
|
||||
"hash":"000000006f82a384c208ecfa04d05beea02d420f3f398ddda5c7f900de5718ea",
|
||||
"confirmations":124329,
|
||||
"strippedsize":8067,
|
||||
"size":8067,
|
||||
"weight":32268,
|
||||
"height":1288528,
|
||||
"version":536870912,
|
||||
"versionHex":"20000000",
|
||||
"merkleroot":"f1596255c357713c9827a739b17c8445cdcb81c4d336e24516c66f714a8f7030",
|
||||
"tx":["65759382e7047f89f7e80676026c10401d47ce47ea997138251c59ca58f28a03","4d2d0cdd89061ce4bb8bdf5502aec9799e5065d205e8ab75c6598bd90a0e6e4c",[...],"a6d2fda52467aa7ca1271529ded5510bd12ad58f99f73fe995f50691aea4eb06"],
|
||||
"time":1521404668,
|
||||
"mediantime":1521398617,
|
||||
"nonce":4209349744,
|
||||
"bits":"1d00ffff",
|
||||
"difficulty":1,
|
||||
"chainwork":"000000000000000000000000000000000000000000000037e37554821063611f",
|
||||
"nTx":32,
|
||||
"previousblockhash":"000000005b55cefec377f7f73e656fef835a928b6eeb2060a89e7eb23a573c49",
|
||||
"nextblockhash":"000000000ab13797b6dddd5e28d2ed62b937cd65ecf49b1f9d75108b9ea500f9"
|
||||
},
|
||||
"error":null,
|
||||
"id":null
|
||||
}
|
||||
```
|
||||
|
||||
### Get the Best Block Info (called by application)
|
||||
|
||||
Returns best block info: calls getblockinfo with bestblockhash.
|
||||
|
||||
```http
|
||||
GET http://cyphernode:8888/getbestblockinfo
|
||||
```
|
||||
|
||||
Proxy response:
|
||||
|
||||
```json
|
||||
{
|
||||
"result":
|
||||
{
|
||||
"hash":"000000006f82a384c208ecfa04d05beea02d420f3f398ddda5c7f900de5718ea",
|
||||
"confirmations":124329,
|
||||
"strippedsize":8067,
|
||||
"size":8067,
|
||||
"weight":32268,
|
||||
"height":1288528,
|
||||
"version":536870912,
|
||||
"versionHex":"20000000",
|
||||
"merkleroot":"f1596255c357713c9827a739b17c8445cdcb81c4d336e24516c66f714a8f7030",
|
||||
"tx":["65759382e7047f89f7e80676026c10401d47ce47ea997138251c59ca58f28a03","4d2d0cdd89061ce4bb8bdf5502aec9799e5065d205e8ab75c6598bd90a0e6e4c",[...],"a6d2fda52467aa7ca1271529ded5510bd12ad58f99f73fe995f50691aea4eb06"],
|
||||
"time":1521404668,
|
||||
"mediantime":1521398617,
|
||||
"nonce":4209349744,
|
||||
"bits":"1d00ffff",
|
||||
"difficulty":1,
|
||||
"chainwork":"000000000000000000000000000000000000000000000037e37554821063611f",
|
||||
"nTx":32,
|
||||
"previousblockhash":"000000005b55cefec377f7f73e656fef835a928b6eeb2060a89e7eb23a573c49",
|
||||
"nextblockhash":"000000000ab13797b6dddd5e28d2ed62b937cd65ecf49b1f9d75108b9ea500f9"
|
||||
},
|
||||
"error":null,
|
||||
"id":null
|
||||
}
|
||||
```
|
||||
|
||||
### Get a transaction details (node's getrawtransaction) (called by application)
|
||||
|
||||
Calls getrawtransaction RPC for the supplied txid.
|
||||
|
||||
```http
|
||||
GET http://cyphernode:8888/gettransaction/af867c86000da76df7ddb1054b273ca9e034e8c89d049b5b2795f9f590f67648
|
||||
```
|
||||
|
||||
Proxy response:
|
||||
|
||||
```json
|
||||
{
|
||||
"result":
|
||||
{
|
||||
"txid":"af867c86000da76df7ddb1054b273ca9e034e8c89d049b5b2795f9f590f67648",
|
||||
"hash":"af867c86000da76df7ddb1054b273ca9e034e8c89d049b5b2795f9f590f67648",
|
||||
"version":1,
|
||||
"size":223,
|
||||
"vsize":223,
|
||||
"locktime":0,
|
||||
"vin":[
|
||||
{
|
||||
"txid":"53a0e2ffa456b97d3944b652bba771221e60f4d852546bd6b351d33261b3e8b6",
|
||||
"vout":0,
|
||||
"scriptSig":
|
||||
{
|
||||
"asm":"30440220127c4adc1cf985cd884c383e69440ce4d48a0c4fdce6bf9d70faa0ee8092acb80220632cb6c99ded7f261814e602fc8fa8e7fe8cb6a95d45c497846b8624f7d19b3c[ALL] 03df001c8b58ac42b6cbfc2223b8efaa7e9a1911e529bd2c8b7f90140079034e75",
|
||||
"hex":"4730440220127c4adc1cf985cd884c383e69440ce4d48a0c4fdce6bf9d70faa0ee8092acb80220632cb6c99ded7f261814e602fc8fa8e7fe8cb6a95d45c497846b8624f7d19b3c012103df001c8b58ac42b6cbfc2223b8efaa7e9a1911e529bd2c8b7f90140079034e75"
|
||||
},
|
||||
"sequence":4294967295
|
||||
}
|
||||
],
|
||||
"vout":[
|
||||
{
|
||||
"value":0.84000000,
|
||||
"n":0,
|
||||
"scriptPubKey":
|
||||
{
|
||||
"asm":"OP_HASH160 c449a7fafb3b13b2952e064f2c3c58e851bb9430 OP_EQUAL",
|
||||
"hex":"a914c449a7fafb3b13b2952e064f2c3c58e851bb943087",
|
||||
"reqSigs":1,
|
||||
"type":"scripthash",
|
||||
"addresses":[
|
||||
"2NB96fbwy8eoHttuZTtbwvvhEYrBwz494ov"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"value":0.01890000,
|
||||
"n":1,
|
||||
"scriptPubKey":
|
||||
{
|
||||
"asm":"OP_DUP OP_HASH160 b0379374df5eab8be9a21ee96711712bdb781a95 OP_EQUALVERIFY OP_CHECKSIG",
|
||||
"hex":"76a914b0379374df5eab8be9a21ee96711712bdb781a9588ac",
|
||||
"reqSigs":1,
|
||||
"type":"pubkeyhash",
|
||||
"addresses":[
|
||||
"mwahoJcaVuy2TiMtGDZV9PaujFeD9z1a1q"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"hex":"0100000001b6e8b36132d351b3d66b5452d8f4601e2271a7bb52b644397db9[...]4df5eab8be9a21ee96711712bdb781a9588ac00000000",
|
||||
"blockhash":"000000009249e7d725cc087cb781ade1dbfaf2bd777822948d5fccd4044f8299",
|
||||
"confirmations":1106162,
|
||||
"time":1415240575,
|
||||
"blocktime":1415240575
|
||||
},
|
||||
"error":null,
|
||||
"id":null
|
||||
}
|
||||
```
|
||||
|
||||
### Try missed callbacks (called by proxycronnode, a CRON job that retries if something went wrong)
|
||||
[See cron_docker](../cron_docker)
|
||||
|
||||
Looks in DB for watched addresses, ask the watching Bitcoin node if those addresses got payments, if so it executes the callbacks that would be usually executed when "conf" is called by the node. This is useful if the watching node went down or there was a network glitch when a transaction on a watched address got confirmed.
|
||||
|
||||
```http
|
||||
GET http://cyphernode:8888/executecallbacks
|
||||
```
|
||||
|
||||
Proxy response: EMPTY
|
||||
|
||||
### Get spending wallet's balance (called by application)
|
||||
|
||||
Calls getbalance RPC on the spending wallet.
|
||||
|
||||
```http
|
||||
GET http://cyphernode:8888/getbalance
|
||||
```
|
||||
|
||||
Proxy response:
|
||||
|
||||
```json
|
||||
{
|
||||
"balance":1.51911837
|
||||
}
|
||||
```
|
||||
|
||||
### Get a new Bitcoin address from spending wallet (called by application)
|
||||
|
||||
Calls getnewaddress RPC on the spending wallet. Used to refill the spending wallet from cold wallet (ie Trezor).
|
||||
|
||||
```http
|
||||
GET http://cyphernode:8888/getnewaddress
|
||||
```
|
||||
|
||||
Proxy response:
|
||||
|
||||
```json
|
||||
{
|
||||
"address":"2NEC972DZpRM7SfuJUG9rYEix2P9A8qsNKF"
|
||||
}
|
||||
```
|
||||
|
||||
### Spend coins from spending wallet (called by application)
|
||||
|
||||
Calls sendtoaddress RPC on the spending wallet with supplied info.
|
||||
|
||||
```http
|
||||
POST http://cyphernode:8888/spend
|
||||
with body...
|
||||
{"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","amount":0.00233}
|
||||
```
|
||||
|
||||
Proxy response:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "accepted",
|
||||
"hash": "af867c86000da76df7ddb1054b273ca9e034e8c89d049b5b2795f9f590f67648"
|
||||
}
|
||||
```
|
||||
|
||||
### Add an output to the next batched transaction (called by application)
|
||||
|
||||
Inserts output information in the DB. Used when batchspend is called later.
|
||||
|
||||
```http
|
||||
POST http://cyphernode:8888/addtobatch
|
||||
with body...
|
||||
{"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","amount":0.00233}
|
||||
```
|
||||
|
||||
Proxy response: EMPTY
|
||||
|
||||
### Spend a batched transaction with outputs added with addtobatch (called by application)
|
||||
|
||||
Calls sendmany RPC on spending wallet with the unspent "addtobatch" inserted outputs. Will be useful during next bull run.
|
||||
|
||||
```http
|
||||
GET http://cyphernode:8888/batchspend
|
||||
```
|
||||
|
||||
Proxy response:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "accepted",
|
||||
"hash": "af867c86000da76df7ddb1054b273ca9e034e8c89d049b5b2795f9f590f67648"
|
||||
}
|
||||
```
|
||||
|
||||
### Get derived address(es) using path in config and provided index (called by application)
|
||||
|
||||
Derives addresses for supplied index. Must be used with derivation.pub32 and derivation.path properties in config.properties.
|
||||
|
||||
```http
|
||||
GET http://cyphernode:8888/deriveindex/25-30
|
||||
GET http://cyphernode:8888/deriveindex/34
|
||||
```
|
||||
|
||||
Proxy response:
|
||||
|
||||
```json
|
||||
{
|
||||
"addresses":[
|
||||
{"address":"2N6Q9kBcLtNswgMSLSQ5oduhbctk7hxEJW8"},
|
||||
{"address":"2NFLhFghAPKEPuZCKoeXYYxuaBxhKXbmhBV"},
|
||||
{"address":"2N7gepbQtRM5Hm4PTjvGadj9wAwEwnAsKiP"},
|
||||
{"address":"2Mth8XDZpXkY9d95tort8HYEAuEesow2tF6"},
|
||||
{"address":"2MwqEmAXhUw6H7bJwMhD13HGWVEj2HgFiNH"},
|
||||
{"address":"2N2Y4BVRdrRFhweub2ehHXveGZC3nryMEJw"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Get derived address(es) using provided path and index (called by application)
|
||||
|
||||
Derives addresses for supplied pub32 and path. config.properties' derivation.pub32 and derivation.path are not used.
|
||||
|
||||
```http
|
||||
POST http://cyphernode:8888/derivepubpath
|
||||
with body...
|
||||
{"pub32":"tpubD6NzVbkrYhZ4YR3QK2tyfMMvBghAvqtNaNK1LTyDWcRHLcMUm3ZN2cGm5BS3MhCRCeCkXQkTXXjiJgqxpqXK7PeUSp86DTTgkLpcjMtpKWk","path":"0/25-30"}
|
||||
|
||||
or
|
||||
|
||||
{"pub32":"upub5GtUcgGed1aGH4HKQ3vMYrsmLXwmHhS1AeX33ZvDgZiyvkGhNTvGd2TA5Lr4v239Fzjj4ZY48t6wTtXUy2yRgapf37QHgt6KWEZ6bgsCLpb","path":"0/34"}
|
||||
|
||||
or
|
||||
|
||||
{"pub32":"vpub5SLqN2bLY4WeZF3kL4VqiWF1itbf3A6oRrq9aPf16AZMVWYCuN9TxpAZwCzVgW94TNzZPNc9XAHD4As6pdnExBtCDGYRmNJrcJ4eV9hNqcv","path":"0/25-30"}
|
||||
```
|
||||
|
||||
Proxy response:
|
||||
|
||||
```json
|
||||
{
|
||||
"addresses":[
|
||||
{"address":"mz3bWMW3BWGT9YGDjJwS8TfhJMMtZ91Frm"},
|
||||
{"address":"mkjmKEX3KJrVpiqLSSxKB6jjgm3WhPnrv8"},
|
||||
{"address":"mk43Tmf6E5nsmETTaNMTZK9TikaeVJRJ4a"},
|
||||
{"address":"n1SEcVHHKpHyNr695JpXNdH6b9cWQ26qkt"},
|
||||
{"address":"mzWqwZkA31kYVy1kpMoZgvfzSDyGgEi7Yg"},
|
||||
{"address":"mp5jtEDNa88xfSQGs5yYQGk7guGWvaG4ci"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Get info from Lightning Network node (called by application)
|
||||
|
||||
Calls getinfo from lightningd. Useful to let your users know where to connect to.
|
||||
|
||||
```http
|
||||
GET http://cyphernode:8888/ln_getinfo
|
||||
```
|
||||
|
||||
Proxy response:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "03lightningnode",
|
||||
"alias": "SatoshiPortal08",
|
||||
"color": "008000",
|
||||
"address": [
|
||||
],
|
||||
"binding": [
|
||||
{
|
||||
"type": "ipv6",
|
||||
"address": "::",
|
||||
"port": 9735
|
||||
},
|
||||
{
|
||||
"type": "ipv4",
|
||||
"address": "0.0.0.0",
|
||||
"port": 9735
|
||||
}
|
||||
],
|
||||
"version": "v0.6.1rc1-40-gae61f64",
|
||||
"blockheight": 1412861,
|
||||
"network": "testnet"
|
||||
}
|
||||
```
|
||||
|
||||
### Create a Lightning Network invoice (called by application)
|
||||
|
||||
Returns a LN invoice. Label must be unique. Description will be used by your user for payment. Expiry is in seconds.
|
||||
|
||||
```http
|
||||
POST http://cyphernode:8888/ln_create_invoice
|
||||
with body...
|
||||
{"msatoshi":10000,"label":"koNCcrSvhX3dmyFhW","description":"Bylls order #10649","expiry":900}
|
||||
```
|
||||
|
||||
Proxy response:
|
||||
|
||||
```json
|
||||
{
|
||||
"payment_hash": "fd27edf261d4b089c3478dece4f2c92c8c68db7be3999e89d452d39c083ad00f",
|
||||
"expires_at": 1536593926,
|
||||
"bolt11": "lntb100n1pdedryzpp5l5n7munp6jcgns683hkwfukf9jxx3kmmuwveazw52tfeczp66q8sdqagfukcmrnyphhyer9wgszxvfsxc6rjxqzuycqp2ak5feh7x7wkkt76uc5ptzcv90jhzhs5swzefv9344hnv74c25dvsstx7l24y46sx5tnkenu480pe06wtly2h5lrj63vszzgrxt4grkcqcltquj"
|
||||
}
|
||||
```
|
||||
|
||||
### Pay a Lightning Network invoice (called by application)
|
||||
|
||||
Make a LN payment. expected_msatoshi and expected_description are respectively the amount and description you gave your user for her to create the invoice; they must match the given bolt11 invoice supplied by your user.
|
||||
|
||||
```http
|
||||
POST http://cyphernode:8888/ln_pay
|
||||
with body...
|
||||
{"bolt11":"lntb1pdca82tpp5gv8mn5jqlj6xztpnt4r472zcyrwf3y2c3cvm4uzg2gqcnj90f83qdp2gf5hgcm0d9hzqnm4w3kx2apqdaexgetjyq3nwvpcxgcqp2g3d86wwdfvyxcz7kce7d3n26d2rw3wf5tzpm2m5fl2z3mm8msa3xk8nv2y32gmzlhwjved980mcmkgq83u9wafq9n4w28amnmwzujgqpmapcr3","expected_msatoshi":10000,"expected_description":"Bitcoin Outlet order #7082"}
|
||||
```
|
||||
|
||||
Proxy response:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 9,
|
||||
"payment_hash": "85b8e69733202e126620e7745be9e23a6b544b758145d86848f3e513e6e1ca42",
|
||||
"destination": "03whatever",
|
||||
"msatoshi": 50000000,
|
||||
"msatoshi_sent": 10000,
|
||||
"created_at": 1537025047,
|
||||
"status": "complete",
|
||||
"payment_preimage": "fececdc787a007a721a1945b70cb022149cc2ee4268964c99ba37a877bded664",
|
||||
"description": "Bitcoin Outlet order #7082",
|
||||
"getroute_tries": 1,
|
||||
"sendpay_tries": 1,
|
||||
"route": [
|
||||
{
|
||||
"id": "03whatever",
|
||||
"channel": "1413467:78:0",
|
||||
"msatoshi": 10000,
|
||||
"delay": 10
|
||||
}
|
||||
],
|
||||
"failures": [
|
||||
]
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Get a new Bitcoin address from the Lightning Network node (to fund it) (called by application)
|
||||
|
||||
Returns a Bitcoin bech32 address to fund your LN wallet.
|
||||
|
||||
```http
|
||||
GET http://cyphernode:8888/ln_newaddr
|
||||
```
|
||||
|
||||
Proxy response:
|
||||
|
||||
```json
|
||||
{
|
||||
"address": "tb1q9n8jfwe9qvlgczfxa5n4pe7haarqflzerqfhk9"
|
||||
}
|
||||
```
|
||||
118
doc/INSTALL.md
Normal file
118
doc/INSTALL.md
Normal file
@@ -0,0 +1,118 @@
|
||||
# Cyphernode
|
||||
|
||||
Indirection layer between client and Bitcoin-related services.
|
||||
|
||||
Here's the plan:
|
||||
|
||||
- The containers are not publicly exposing ports.
|
||||
- Everything is accessible exclusively within the encrypted overlay network.
|
||||
- If your system is distributed:
|
||||
- ...should be doubly encrypted by an OpenVPN tunnel
|
||||
- ...the hosts should be secured and the VPN tunnel should have limited scope by iptables rules on each host.
|
||||
- We can have different Bitcoin Nodes for watching and spending, giving the flexibility to have different security models one each.
|
||||
- Only the Proxy has Bitcoin Node RPC credentials.
|
||||
- The Proxy is exclusively accessible by the Overlay network's containers.
|
||||
- To manually manage the Proxy (and have access to it), one has to gain access to the Docker host servers as a docker user.
|
||||
- **Coming soon**: added security to use the spending features of the Proxy with Trezor and Coldcard.
|
||||
|
||||
## Setting up
|
||||
|
||||
Default setup assumes your Bitcoin Node is already running somewhere. The reason is that it takes a lot of disk space and often already exists in your infrastructure, why not reusing it. After all, full blockchain sync takes a while.
|
||||
|
||||
You could also just uncomment it in the docker-compose file. If you run it in pruned mode, say so in config.properties. The computefees feature won't work in pruned mode.
|
||||
|
||||
### Set the swarm
|
||||
(10.8.0.2 is the host's VPN IP address)
|
||||
|
||||
```shell
|
||||
debian@dev:~/dev/Cyphernode$ docker swarm init --task-history-limit 1 --advertise-addr 10.8.0.2
|
||||
Swarm initialized: current node (hufy324d291dyakizsuvjd0uw) is now a manager.
|
||||
|
||||
To add a worker to this swarm, run the following command:
|
||||
|
||||
docker swarm join --token SWMTKN-1-2pxouynn9g8si42e8g9ujwy0v9po45axx367fy0fkjhzo3l1z8-75nirjfkobl7htvpfh986pyz3 10.8.0.2:2377
|
||||
|
||||
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
|
||||
```
|
||||
|
||||
### Create the Overlay Network and make sure your app joins it!
|
||||
(if your app is not a Docker container, you will have to expose Cyphernode's port and secure it! In that case, use a reverse proxy with TLS)
|
||||
|
||||
```shell
|
||||
debian@dev:~/dev/Cyphernode$ docker network create --driver=overlay --attachable --opt encrypted cyphernodenet
|
||||
debian@dev:~/dev/Cyphernode$ docker network connect cyphernodenet yourappcontainer
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
```shell
|
||||
debian@dev:~/dev/Cyphernode$ vi proxy_docker/env.properties
|
||||
debian@dev:~/dev/Cyphernode$ vi proxy_docker/app/config/derivation.properties
|
||||
debian@dev:~/dev/Cyphernode$ vi proxy_docker/app/config/watcher_btcnode_curlcfg.properties
|
||||
debian@dev:~/dev/Cyphernode$ vi proxy_docker/app/config/spender_btcnode_curlcfg.properties
|
||||
debian@dev:~/dev/Cyphernode$ vi proxy_docker/app/config/config.properties
|
||||
debian@dev:~/dev/Cyphernode$ vi cron_docker/env.properties
|
||||
debian@dev:~/dev/Cyphernode$ vi pycoin_docker/env.properties
|
||||
```
|
||||
|
||||
### Build cron image
|
||||
|
||||
[See how to build proxycron image](../cron_docker)
|
||||
|
||||
### Build btcproxy image
|
||||
|
||||
[See how to build btcproxy image](../proxy_docker)
|
||||
|
||||
### Build pycoin image
|
||||
|
||||
[See how to build pycoin image](../pycoin_docker)
|
||||
|
||||
### Build clightning image
|
||||
|
||||
[See how to build clightning image](https://github.com/SatoshiPortal/dockers/tree/master/rpi/LN/c-lightning)
|
||||
|
||||
### Deploy
|
||||
|
||||
```shell
|
||||
debian@dev:~/dev/Cyphernode$ docker stack deploy --compose-file docker-compose.yml cyphernodestack
|
||||
Creating service cyphernodestack_cyphernode
|
||||
Creating service cyphernodestack_proxycronnode
|
||||
Creating service cyphernodestack_pycoinnode
|
||||
Creating service cyphernodestack_clightningnode
|
||||
```
|
||||
|
||||
## Off-site Bitcoin Node
|
||||
|
||||
### Join swarm created on Cyphernode server
|
||||
|
||||
```shell
|
||||
pi@SP-BTC01:~ $ docker swarm join --token SWMTKN-1-2pxouynn9g8si42e8g9ujwy0v9po45axx367fy0fkjhzo3l1z8-75nirjfkobl7htvpfh986pyz3 10.8.0.2:2377
|
||||
```
|
||||
|
||||
### Build node container image
|
||||
|
||||
[See how to build Bitcoin Node image](https://github.com/SatoshiPortal/dockers/tree/master/rpi/bitcoin-core)
|
||||
|
||||
### Run node or connect already-running node
|
||||
|
||||
```shell
|
||||
pi@SP-BTC01:~ $ docker run -d --rm --mount type=bind,source="$HOME/.bitcoin",target="/bitcoinuser/.bitcoin" --name btcnode --network cyphernodenet btcnode
|
||||
```
|
||||
|
||||
```shell
|
||||
pi@SP-BTC01:~ $ docker network connect cyphernodenet btcnode
|
||||
```
|
||||
|
||||
## Test deployment (from any host)
|
||||
|
||||
```shell
|
||||
echo "GET /getbestblockinfo" | docker run --rm -i --network=cyphernodenet alpine nc cyphernode:8888 -
|
||||
echo "GET /getbalance" | docker run --rm -i --network=cyphernodenet alpine nc cyphernode:8888 -
|
||||
echo "GET /getbestblockhash" | docker run --rm -i --network=cyphernodenet alpine nc cyphernode:8888 -
|
||||
echo "GET /getblockinfo/00000000a64e0d1ae0c39166f4e8717a672daf3d61bf7bbb41b0f487fcae74d2" | docker run --rm -i --network=cyphernodenet alpine nc cyphernode:8888 -
|
||||
curl -v -H "Content-Type: application/json" -d '{"address":"2MsWyaQ8APbnqasFpWopqUKqsdpiVY3EwLE","amount":0.2}' cyphernode:8888/spend
|
||||
echo "GET /ln_getinfo" | docker run --rm -i --network=cyphernodenet alpine nc cyphernode:8888 -
|
||||
echo "GET /ln_newaddr" | docker run --rm -i --network=cyphernodenet alpine nc cyphernode:8888 -
|
||||
curl -v -H "Content-Type: application/json" -d '{"msatoshi":10000,"label":"koNCcrSvhX3dmyFhW","description":"Bylls order #10649","expiry":900}' cyphernode:8888/ln_create_invoice
|
||||
curl -v -H "Content-Type: application/json" -d '{"bolt11":"lntb1pdca82tpp5gv8mn5jqlj6xztpnt4r472zcyrwf3y2c3cvm4uzg2gqcnj90f83qdp2gf5hgcm0d9hzqnm4w3kx2apqdaexgetjyq3nwvpcxgcqp2g3d86wwdfvyxcz7kce7d3n26d2rw3wf5tzpm2m5fl2z3mm8msa3xk8nv2y32gmzlhwjved980mcmkgq83u9wafq9n4w28amnmwzujgqpmapcr3","msatoshi":10000,"description":"Bitcoin Outlet order #7082"}' cyphernode:8888/ln_pay
|
||||
```
|
||||
75
docker-compose.yml
Normal file
75
docker-compose.yml
Normal file
@@ -0,0 +1,75 @@
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
cyphernode:
|
||||
# Bitcoin Mini Proxy
|
||||
env_file:
|
||||
- proxy_docker/env.properties
|
||||
image: btcproxyimg
|
||||
# ports:
|
||||
# - "8888:8888"
|
||||
volumes:
|
||||
# Variable substitutions don't work
|
||||
# Match with DB_PATH in proxy_docker/env.properties
|
||||
- "~/btcproxydb:/proxyuser/db"
|
||||
- "~/.lightning:/proxyuser/.lightning"
|
||||
deploy:
|
||||
placement:
|
||||
constraints: [node.hostname==dev]
|
||||
networks:
|
||||
- cyphernodenet
|
||||
|
||||
proxycronnode:
|
||||
# Async jobs
|
||||
env_file:
|
||||
- cron_docker/env.properties
|
||||
image: proxycronimg
|
||||
deploy:
|
||||
placement:
|
||||
constraints: [node.hostname==dev]
|
||||
networks:
|
||||
- cyphernodenet
|
||||
|
||||
pycoinnode:
|
||||
# Pycoin
|
||||
env_file:
|
||||
- pycoin_docker/env.properties
|
||||
image: pycoinimg
|
||||
# ports:
|
||||
# - "7777:7777"
|
||||
deploy:
|
||||
placement:
|
||||
constraints: [node.hostname==dev]
|
||||
networks:
|
||||
- cyphernodenet
|
||||
|
||||
clightningnode:
|
||||
# c-lightning lightning network node
|
||||
image: clnimg
|
||||
ports:
|
||||
- "9735:9735"
|
||||
volumes:
|
||||
- "~/.lightning:/lnuser/.lightning"
|
||||
deploy:
|
||||
placement:
|
||||
constraints: [node.hostname==dev]
|
||||
networks:
|
||||
- cyphernodenet
|
||||
|
||||
# spbtcnode:
|
||||
# Bitcoin node
|
||||
# image: btcnode
|
||||
# ports:
|
||||
# - "18333:18333"
|
||||
# - "18332:18332"
|
||||
# - "29000:29000"
|
||||
# - "8333:8333"
|
||||
# - "8332:8332"
|
||||
# volumes:
|
||||
# - "~/.bitcoin:/bitcoinuser/.bitcoin"
|
||||
# networks:
|
||||
# - cyphernodenet
|
||||
|
||||
networks:
|
||||
cyphernodenet:
|
||||
external: true
|
||||
56
proxy_docker/Dockerfile
Normal file
56
proxy_docker/Dockerfile
Normal file
@@ -0,0 +1,56 @@
|
||||
FROM alpine
|
||||
|
||||
ARG USER_ID
|
||||
ARG GROUP_ID
|
||||
ENV USERNAME proxyuser
|
||||
ENV HOME /${USERNAME}
|
||||
ENV USER_ID ${USER_ID:-1000}
|
||||
ENV GROUP_ID ${GROUP_ID:-1000}
|
||||
|
||||
RUN addgroup -g ${GROUP_ID} ${USERNAME} \
|
||||
&& adduser -u ${USER_ID} -G ${USERNAME} -D -s /bin/sh -h ${HOME} ${USERNAME}
|
||||
|
||||
RUN apk add --update --no-cache \
|
||||
sqlite \
|
||||
jq \
|
||||
curl
|
||||
|
||||
COPY --chown=proxyuser app/script/callbacks_job.sh ${HOME}/callbacks_job.sh
|
||||
COPY --chown=proxyuser app/script/blockchainrpc.sh ${HOME}/blockchainrpc.sh
|
||||
COPY --chown=proxyuser app/script/call_lightningd.sh ${HOME}/call_lightningd.sh
|
||||
COPY --chown=proxyuser app/script/bitcoin.sh ${HOME}/bitcoin.sh
|
||||
COPY --chown=proxyuser app/script/requesthandler.sh ${HOME}/requesthandler.sh
|
||||
COPY --chown=proxyuser app/script/watchrequest.sh ${HOME}/watchrequest.sh
|
||||
COPY --chown=proxyuser app/script/walletoperations.sh ${HOME}/walletoperations.sh
|
||||
COPY --chown=proxyuser app/script/confirmation.sh ${HOME}/confirmation.sh
|
||||
COPY --chown=proxyuser app/config/watcher_btcnode_curlcfg.properties ${HOME}/watcher_btcnode_curlcfg.properties
|
||||
COPY --chown=proxyuser app/config/spender_btcnode_curlcfg.properties ${HOME}/spender_btcnode_curlcfg.properties
|
||||
COPY --chown=proxyuser app/config/config.properties ${HOME}/config.properties
|
||||
COPY --chown=proxyuser app/script/startproxy.sh ${HOME}/startproxy.sh
|
||||
COPY --chown=proxyuser app/script/trace.sh ${HOME}/trace.sh
|
||||
COPY --chown=proxyuser app/script/sendtobitcoinnode.sh ${HOME}/sendtobitcoinnode.sh
|
||||
COPY --chown=proxyuser app/script/responsetoclient.sh ${HOME}/responsetoclient.sh
|
||||
COPY --chown=proxyuser app/script/importaddress.sh ${HOME}/importaddress.sh
|
||||
COPY --chown=proxyuser app/script/sql.sh ${HOME}/sql.sh
|
||||
COPY --chown=proxyuser app/data/watching.sql ${HOME}/watching.sql
|
||||
COPY --chown=proxyuser app/script/computefees.sh ${HOME}/computefees.sh
|
||||
COPY --chown=proxyuser app/script/unwatchrequest.sh ${HOME}/unwatchrequest.sh
|
||||
COPY --chown=proxyuser app/script/getactivewatches.sh ${HOME}/getactivewatches.sh
|
||||
COPY --chown=proxyuser app/script/utils.sh ${HOME}/utils.sh
|
||||
COPY --chown=proxyuser app/script/manage_missed_conf.sh ${HOME}/manage_missed_conf.sh
|
||||
COPY --chown=proxyuser app/script/tests.sh ${HOME}/tests.sh
|
||||
COPY --chown=proxyuser app/script/tests-cb.sh ${HOME}/tests-cb.sh
|
||||
COPY --chown=proxyuser app/bin/lightning-cli_x86 ${HOME}/lightning-cli
|
||||
|
||||
USER ${USERNAME}
|
||||
WORKDIR ${HOME}
|
||||
|
||||
RUN chmod +x startproxy.sh requesthandler.sh \
|
||||
&& chmod 600 watcher_btcnode_curlcfg.properties \
|
||||
&& chmod 600 spender_btcnode_curlcfg.properties \
|
||||
&& chmod 600 config.properties \
|
||||
&& mkdir db
|
||||
|
||||
VOLUME ["${HOME}/db", "${HOME}/.lightning"]
|
||||
|
||||
ENTRYPOINT ["./startproxy.sh"]
|
||||
520
proxy_docker/README.md
Normal file
520
proxy_docker/README.md
Normal file
@@ -0,0 +1,520 @@
|
||||
# Cyphernode Proxy
|
||||
|
||||
We assume you are the user pi on a Raspberry Pi.
|
||||
|
||||
## Create proxyuser that will run the processes
|
||||
|
||||
Log in your host and:
|
||||
|
||||
```shell
|
||||
sudo useradd proxyuser
|
||||
```
|
||||
|
||||
## Configure your container by modifying `env.properties` file
|
||||
|
||||
```properties
|
||||
TRACING=1
|
||||
WATCHER_BTC_NODE_RPC_URL=btcnode:18332/wallet/watching01.dat
|
||||
SPENDER_BTC_NODE_RPC_URL=btcnode:18332/wallet/spending01.dat
|
||||
PROXY_LISTENING_PORT=8888
|
||||
# Variable substitutions don't work
|
||||
DB_PATH=/proxyuser/db
|
||||
DB_FILE=/proxyuser/db/proxydb
|
||||
# Pycoin container
|
||||
PYCOIN_CONTAINER=pycoinnode:7777
|
||||
# OTS container
|
||||
OTS_CONTAINER=otsnode:6666
|
||||
```
|
||||
|
||||
## Set your Watching Bitcoin node RPC credentials in `app/config/watcher_btcnode_curlcfg.properties`
|
||||
|
||||
```properties
|
||||
user=rpc_username:rpc_password
|
||||
```
|
||||
|
||||
## Set your Spending Bitcoin node RPC credentials in `app/config/spender_btcnode_curlcfg.properties`
|
||||
|
||||
```properties
|
||||
user=rpc_username:rpc_password
|
||||
```
|
||||
|
||||
## Set your address derivation information in `app/config/derivation.properties`
|
||||
|
||||
```properties
|
||||
derivation.xpub=upub5GtUcgGed1aGH4HKQ3vMYrsmLXwmHhS1AeX33ZvDgZiyvkGhNTvGd2TA5Lr4v239Fzjj4ZY48t6wTtXUy2yRgapf37QHgt6KWEZ6bgsCLpb
|
||||
derivation.path=0/n
|
||||
watchingnode.pruned=false
|
||||
```
|
||||
|
||||
## Building docker image
|
||||
|
||||
```shell
|
||||
docker build -t btcproxyimg --build-arg USER_ID=$(id -u proxyuser) --build-arg GROUP_ID=$(id -g proxyuser) .
|
||||
```
|
||||
|
||||
## Create sqlite3 database path and give rights
|
||||
|
||||
```shell
|
||||
mkdir ~/btcproxydb ; sudo chown -R proxyuser:pi ~/btcproxydb ; sudo chmod g+ws ~/btcproxydb
|
||||
```
|
||||
|
||||
## What you MUST have in your Watching Bitcoin node's bitcoin.conf file
|
||||
(or something similar)
|
||||
(*Not in the Spending Bitcoin node!*)
|
||||
|
||||
```properties
|
||||
walletnotify=curl cyphernode:8888/conf/%s
|
||||
```
|
||||
|
||||
## Current API
|
||||
|
||||
### Watch a Bitcoin Address (called by application)
|
||||
|
||||
```http
|
||||
POST http://cyphernode:8888/watch
|
||||
with body...
|
||||
{"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","unconfirmedCallbackURL":"192.168.111.233:1111/callback0conf","confirmedCallbackURL":"192.168.111.233:1111/callback1conf"}
|
||||
```
|
||||
|
||||
Proxy response:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "291",
|
||||
"event": "watch",
|
||||
"imported": "1",
|
||||
"inserted": "1",
|
||||
"address": "2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp",
|
||||
"unconfirmedCallbackURL": "192.168.133.233:1111/callback0conf",
|
||||
"confirmedCallbackURL": "192.168.133.233:1111/callback1conf",
|
||||
"estimatesmartfee2blocks": "0.000010",
|
||||
"estimatesmartfee6blocks": "0.000010",
|
||||
"estimatesmartfee36blocks": "0.000010",
|
||||
"estimatesmartfee144blocks": "0.000010"
|
||||
}
|
||||
```
|
||||
|
||||
### Un-watch a previously watched Bitcoin Address (called by application)
|
||||
|
||||
```http
|
||||
GET http://cyphernode:8888/unwatch/2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp
|
||||
```
|
||||
|
||||
Proxy response:
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "unwatch",
|
||||
"address": "2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp"
|
||||
}
|
||||
```
|
||||
|
||||
### Get a list of Bitcoin addresses being watched (called by application)
|
||||
|
||||
```http
|
||||
GET http://cyphernode:8888/getactivewatches
|
||||
```
|
||||
|
||||
Proxy response:
|
||||
|
||||
```json
|
||||
{
|
||||
"watches": [
|
||||
{
|
||||
"id":"291",
|
||||
"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp",
|
||||
"imported":"1",
|
||||
"unconfirmedCallbackURL":"192.168.133.233:1111/callback0conf",
|
||||
"confirmedCallbackURL":"192.168.133.233:1111/callback1conf",
|
||||
"watching_since":"2018-09-06 21:14:03"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Confirm a Transaction on Watched Address (called by Bitcoin node on transaction confirmations)
|
||||
|
||||
```http
|
||||
GET http://cyphernode:8888/conf/b081ca7724386f549cf0c16f71db6affeb52ff7a0d9b606fb2e5c43faffd3387
|
||||
```
|
||||
|
||||
Proxy response:
|
||||
|
||||
```json
|
||||
{
|
||||
"result":"confirmed"
|
||||
}
|
||||
```
|
||||
|
||||
### Get the Best Block Hash (called by application)
|
||||
|
||||
```http
|
||||
GET http://cyphernode:8888/getbestblockhash
|
||||
```
|
||||
|
||||
Proxy response:
|
||||
|
||||
```json
|
||||
{
|
||||
"result":"00000000000000262588c21afbf9e1da151daf10b11215d501271163f26ea74a",
|
||||
"error":null,
|
||||
"id":null
|
||||
}
|
||||
```
|
||||
|
||||
### Get Block Info (called by application)
|
||||
|
||||
```http
|
||||
GET http://cyphernode:8888/getblockinfo/000000006f82a384c208ecfa04d05beea02d420f3f398ddda5c7f900de5718ea
|
||||
```
|
||||
|
||||
Proxy response:
|
||||
|
||||
```json
|
||||
{
|
||||
"result":
|
||||
{
|
||||
"hash":"000000006f82a384c208ecfa04d05beea02d420f3f398ddda5c7f900de5718ea",
|
||||
"confirmations":124329,
|
||||
"strippedsize":8067,
|
||||
"size":8067,
|
||||
"weight":32268,
|
||||
"height":1288528,
|
||||
"version":536870912,
|
||||
"versionHex":"20000000",
|
||||
"merkleroot":"f1596255c357713c9827a739b17c8445cdcb81c4d336e24516c66f714a8f7030",
|
||||
"tx":["65759382e7047f89f7e80676026c10401d47ce47ea997138251c59ca58f28a03","4d2d0cdd89061ce4bb8bdf5502aec9799e5065d205e8ab75c6598bd90a0e6e4c",[...],"a6d2fda52467aa7ca1271529ded5510bd12ad58f99f73fe995f50691aea4eb06"],
|
||||
"time":1521404668,
|
||||
"mediantime":1521398617,
|
||||
"nonce":4209349744,
|
||||
"bits":"1d00ffff",
|
||||
"difficulty":1,
|
||||
"chainwork":"000000000000000000000000000000000000000000000037e37554821063611f",
|
||||
"nTx":32,
|
||||
"previousblockhash":"000000005b55cefec377f7f73e656fef835a928b6eeb2060a89e7eb23a573c49",
|
||||
"nextblockhash":"000000000ab13797b6dddd5e28d2ed62b937cd65ecf49b1f9d75108b9ea500f9"
|
||||
},
|
||||
"error":null,
|
||||
"id":null
|
||||
}
|
||||
```
|
||||
|
||||
### Get the Best Block Info (called by application)
|
||||
|
||||
```http
|
||||
GET http://cyphernode:8888/getbestblockinfo
|
||||
```
|
||||
|
||||
Proxy response:
|
||||
|
||||
```json
|
||||
{
|
||||
"result":
|
||||
{
|
||||
"hash":"000000006f82a384c208ecfa04d05beea02d420f3f398ddda5c7f900de5718ea",
|
||||
"confirmations":124329,
|
||||
"strippedsize":8067,
|
||||
"size":8067,
|
||||
"weight":32268,
|
||||
"height":1288528,
|
||||
"version":536870912,
|
||||
"versionHex":"20000000",
|
||||
"merkleroot":"f1596255c357713c9827a739b17c8445cdcb81c4d336e24516c66f714a8f7030",
|
||||
"tx":["65759382e7047f89f7e80676026c10401d47ce47ea997138251c59ca58f28a03","4d2d0cdd89061ce4bb8bdf5502aec9799e5065d205e8ab75c6598bd90a0e6e4c",[...],"a6d2fda52467aa7ca1271529ded5510bd12ad58f99f73fe995f50691aea4eb06"],
|
||||
"time":1521404668,
|
||||
"mediantime":1521398617,
|
||||
"nonce":4209349744,
|
||||
"bits":"1d00ffff",
|
||||
"difficulty":1,
|
||||
"chainwork":"000000000000000000000000000000000000000000000037e37554821063611f",
|
||||
"nTx":32,
|
||||
"previousblockhash":"000000005b55cefec377f7f73e656fef835a928b6eeb2060a89e7eb23a573c49",
|
||||
"nextblockhash":"000000000ab13797b6dddd5e28d2ed62b937cd65ecf49b1f9d75108b9ea500f9"
|
||||
},
|
||||
"error":null,
|
||||
"id":null
|
||||
}
|
||||
```
|
||||
|
||||
### Get a transaction details (node's getrawtransaction) (called by application)
|
||||
|
||||
```http
|
||||
GET http://cyphernode:8888/gettransaction/af867c86000da76df7ddb1054b273ca9e034e8c89d049b5b2795f9f590f67648
|
||||
```
|
||||
|
||||
Proxy response:
|
||||
|
||||
```json
|
||||
{
|
||||
"result":
|
||||
{
|
||||
"txid":"af867c86000da76df7ddb1054b273ca9e034e8c89d049b5b2795f9f590f67648",
|
||||
"hash":"af867c86000da76df7ddb1054b273ca9e034e8c89d049b5b2795f9f590f67648",
|
||||
"version":1,
|
||||
"size":223,
|
||||
"vsize":223,
|
||||
"locktime":0,
|
||||
"vin":[
|
||||
{
|
||||
"txid":"53a0e2ffa456b97d3944b652bba771221e60f4d852546bd6b351d33261b3e8b6",
|
||||
"vout":0,
|
||||
"scriptSig":
|
||||
{
|
||||
"asm":"30440220127c4adc1cf985cd884c383e69440ce4d48a0c4fdce6bf9d70faa0ee8092acb80220632cb6c99ded7f261814e602fc8fa8e7fe8cb6a95d45c497846b8624f7d19b3c[ALL] 03df001c8b58ac42b6cbfc2223b8efaa7e9a1911e529bd2c8b7f90140079034e75",
|
||||
"hex":"4730440220127c4adc1cf985cd884c383e69440ce4d48a0c4fdce6bf9d70faa0ee8092acb80220632cb6c99ded7f261814e602fc8fa8e7fe8cb6a95d45c497846b8624f7d19b3c012103df001c8b58ac42b6cbfc2223b8efaa7e9a1911e529bd2c8b7f90140079034e75"
|
||||
},
|
||||
"sequence":4294967295
|
||||
}
|
||||
],
|
||||
"vout":[
|
||||
{
|
||||
"value":0.84000000,
|
||||
"n":0,
|
||||
"scriptPubKey":
|
||||
{
|
||||
"asm":"OP_HASH160 c449a7fafb3b13b2952e064f2c3c58e851bb9430 OP_EQUAL",
|
||||
"hex":"a914c449a7fafb3b13b2952e064f2c3c58e851bb943087",
|
||||
"reqSigs":1,
|
||||
"type":"scripthash",
|
||||
"addresses":[
|
||||
"2NB96fbwy8eoHttuZTtbwvvhEYrBwz494ov"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"value":0.01890000,
|
||||
"n":1,
|
||||
"scriptPubKey":
|
||||
{
|
||||
"asm":"OP_DUP OP_HASH160 b0379374df5eab8be9a21ee96711712bdb781a95 OP_EQUALVERIFY OP_CHECKSIG",
|
||||
"hex":"76a914b0379374df5eab8be9a21ee96711712bdb781a9588ac",
|
||||
"reqSigs":1,
|
||||
"type":"pubkeyhash",
|
||||
"addresses":[
|
||||
"mwahoJcaVuy2TiMtGDZV9PaujFeD9z1a1q"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"hex":"0100000001b6e8b36132d351b3d66b5452d8f4601e2271a7bb52b644397db9[...]4df5eab8be9a21ee96711712bdb781a9588ac00000000",
|
||||
"blockhash":"000000009249e7d725cc087cb781ade1dbfaf2bd777822948d5fccd4044f8299",
|
||||
"confirmations":1106162,
|
||||
"time":1415240575,
|
||||
"blocktime":1415240575
|
||||
},
|
||||
"error":null,
|
||||
"id":null
|
||||
}
|
||||
```
|
||||
|
||||
### Try missed callbacks (called by proxycronnode, a CRON job that retries if something went wrong)
|
||||
[See cron_docker](../cron_docker)
|
||||
|
||||
```http
|
||||
GET http://cyphernode:8888/executecallbacks
|
||||
```
|
||||
|
||||
Proxy response: EMPTY
|
||||
|
||||
### Get spending wallet's balance (called by application)
|
||||
|
||||
```http
|
||||
GET http://cyphernode:8888/getbalance
|
||||
```
|
||||
|
||||
Proxy response:
|
||||
|
||||
```json
|
||||
{
|
||||
"balance":1.51911837
|
||||
}
|
||||
```
|
||||
|
||||
### Get a new Bitcoin address from spending wallet (called by application)
|
||||
|
||||
```http
|
||||
GET http://cyphernode:8888/getnewaddress
|
||||
```
|
||||
|
||||
Proxy response:
|
||||
|
||||
```json
|
||||
{
|
||||
"address":"2NEC972DZpRM7SfuJUG9rYEix2P9A8qsNKF"
|
||||
}
|
||||
```
|
||||
|
||||
### Spend coins from spending wallet (called by application)
|
||||
|
||||
```http
|
||||
POST http://cyphernode:8888/spend
|
||||
with body...
|
||||
{"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","amount":0.00233}
|
||||
```
|
||||
|
||||
Proxy response:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "accepted",
|
||||
"hash": "af867c86000da76df7ddb1054b273ca9e034e8c89d049b5b2795f9f590f67648"
|
||||
}
|
||||
```
|
||||
|
||||
### Add an output to the next batched transaction (called by application)
|
||||
|
||||
```http
|
||||
POST http://cyphernode:8888/addtobatch
|
||||
with body...
|
||||
{"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","amount":0.00233}
|
||||
```
|
||||
|
||||
Proxy response: EMPTY
|
||||
|
||||
### Spend a batched transaction with outputs added with addtobatch (called by application)
|
||||
|
||||
```http
|
||||
GET http://cyphernode:8888/batchspend
|
||||
```
|
||||
|
||||
Proxy response:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "accepted",
|
||||
"hash": "af867c86000da76df7ddb1054b273ca9e034e8c89d049b5b2795f9f590f67648"
|
||||
}
|
||||
```
|
||||
|
||||
### Get derived address(es) using path in config and provided index (called by application)
|
||||
|
||||
```http
|
||||
GET http://cyphernode:8888/deriveindex/25-30
|
||||
GET http://cyphernode:8888/deriveindex/34
|
||||
```
|
||||
|
||||
Proxy response:
|
||||
|
||||
```json
|
||||
{
|
||||
"addresses":[
|
||||
{"address":"2N6Q9kBcLtNswgMSLSQ5oduhbctk7hxEJW8"},
|
||||
{"address":"2NFLhFghAPKEPuZCKoeXYYxuaBxhKXbmhBV"},
|
||||
{"address":"2N7gepbQtRM5Hm4PTjvGadj9wAwEwnAsKiP"},
|
||||
{"address":"2Mth8XDZpXkY9d95tort8HYEAuEesow2tF6"},
|
||||
{"address":"2MwqEmAXhUw6H7bJwMhD13HGWVEj2HgFiNH"},
|
||||
{"address":"2N2Y4BVRdrRFhweub2ehHXveGZC3nryMEJw"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Get derived address(es) using provided path and index (called by application)
|
||||
|
||||
```http
|
||||
POST http://cyphernode:8888/derivepubpath
|
||||
with body...
|
||||
{"pub32":"tpubD6NzVbkrYhZ4YR3QK2tyfMMvBghAvqtNaNK1LTyDWcRHLcMUm3ZN2cGm5BS3MhCRCeCkXQkTXXjiJgqxpqXK7PeUSp86DTTgkLpcjMtpKWk","path":"0/25-30"}
|
||||
|
||||
or
|
||||
|
||||
{"pub32":"upub5GtUcgGed1aGH4HKQ3vMYrsmLXwmHhS1AeX33ZvDgZiyvkGhNTvGd2TA5Lr4v239Fzjj4ZY48t6wTtXUy2yRgapf37QHgt6KWEZ6bgsCLpb","path":"0/34"}
|
||||
|
||||
or
|
||||
|
||||
{"pub32":"vpub5SLqN2bLY4WeZF3kL4VqiWF1itbf3A6oRrq9aPf16AZMVWYCuN9TxpAZwCzVgW94TNzZPNc9XAHD4As6pdnExBtCDGYRmNJrcJ4eV9hNqcv","path":"0/25-30"}
|
||||
```
|
||||
|
||||
Proxy response:
|
||||
|
||||
```json
|
||||
{
|
||||
"addresses":[
|
||||
{"address":"mz3bWMW3BWGT9YGDjJwS8TfhJMMtZ91Frm"},
|
||||
{"address":"mkjmKEX3KJrVpiqLSSxKB6jjgm3WhPnrv8"},
|
||||
{"address":"mk43Tmf6E5nsmETTaNMTZK9TikaeVJRJ4a"},
|
||||
{"address":"n1SEcVHHKpHyNr695JpXNdH6b9cWQ26qkt"},
|
||||
{"address":"mzWqwZkA31kYVy1kpMoZgvfzSDyGgEi7Yg"},
|
||||
{"address":"mp5jtEDNa88xfSQGs5yYQGk7guGWvaG4ci"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Get info from Lightning Network node (called by application)
|
||||
|
||||
```http
|
||||
GET http://cyphernode:8888/ln_getinfo
|
||||
```
|
||||
|
||||
Proxy response:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "03bb990f43e6a6eccb223288d32fcb91209b12370c0a8bf5cdf4ad7bc11e33f253",
|
||||
"alias": "SatoshiPortal01",
|
||||
"color": "008000",
|
||||
"address": [
|
||||
],
|
||||
"binding": [
|
||||
{
|
||||
"type": "ipv6",
|
||||
"address": "::",
|
||||
"port": 9735
|
||||
},
|
||||
{
|
||||
"type": "ipv4",
|
||||
"address": "0.0.0.0",
|
||||
"port": 9735
|
||||
}
|
||||
],
|
||||
"version": "v0.6.1rc1-40-gae61f64",
|
||||
"blockheight": 1412861,
|
||||
"network": "testnet"
|
||||
}
|
||||
```
|
||||
|
||||
### Create a Lightning Network invoice (called by application)
|
||||
|
||||
```http
|
||||
POST http://cyphernode:8888/ln_create_invoice
|
||||
with body...
|
||||
{"msatoshi":10000,"label":"koNCcrSvhX3dmyFhW","description":"Bylls order #10649","expiry":900}
|
||||
```
|
||||
|
||||
Proxy response:
|
||||
|
||||
```json
|
||||
{
|
||||
"payment_hash": "fd27edf261d4b089c3478dece4f2c92c8c68db7be3999e89d452d39c083ad00f",
|
||||
"expires_at": 1536593926,
|
||||
"bolt11": "lntb100n1pdedryzpp5l5n7munp6jcgns683hkwfukf9jxx3kmmuwveazw52tfeczp66q8sdqagfukcmrnyphhyer9wgszxvfsxc6rjxqzuycqp2ak5feh7x7wkkt76uc5ptzcv90jhzhs5swzefv9344hnv74c25dvsstx7l24y46sx5tnkenu480pe06wtly2h5lrj63vszzgrxt4grkcqcltquj"
|
||||
}
|
||||
```
|
||||
|
||||
### Pay a Lightning Network invoice (called by application)
|
||||
|
||||
```http
|
||||
POST http://cyphernode:8888/ln_pay
|
||||
with body...
|
||||
{"bolt11":"lntb1pdca82tpp5gv8mn5jqlj6xztpnt4r472zcyrwf3y2c3cvm4uzg2gqcnj90f83qdp2gf5hgcm0d9hzqnm4w3kx2apqdaexgetjyq3nwvpcxgcqp2g3d86wwdfvyxcz7kce7d3n26d2rw3wf5tzpm2m5fl2z3mm8msa3xk8nv2y32gmzlhwjved980mcmkgq83u9wafq9n4w28amnmwzujgqpmapcr3","expected_msatoshi":10000,"expected_description":"Bitcoin Outlet order #7082"}
|
||||
```
|
||||
|
||||
Proxy response:
|
||||
|
||||
```json
|
||||
{
|
||||
"": ""
|
||||
}
|
||||
```
|
||||
|
||||
### Get a new Bitcoin address from the Lightning Network node (to fund it) (called by application)
|
||||
|
||||
```http
|
||||
GET http://cyphernode:8888/ln_newaddr
|
||||
```
|
||||
|
||||
Proxy response:
|
||||
|
||||
```json
|
||||
{
|
||||
"address": "tb1q9n8jfwe9qvlgczfxa5n4pe7haarqflzerqfhk9"
|
||||
}
|
||||
```
|
||||
5
proxy_docker/app/bin/README.md
Normal file
5
proxy_docker/app/bin/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Nota bene
|
||||
|
||||
lightning-cli binary has been pre-compiled in an Alpine docker container.
|
||||
|
||||
Use lightning-cli_arm for armhf architecture, lightning-cli_x86 for x86_64.
|
||||
BIN
proxy_docker/app/bin/lightning-cli_arm
Normal file
BIN
proxy_docker/app/bin/lightning-cli_arm
Normal file
Binary file not shown.
BIN
proxy_docker/app/bin/lightning-cli_x86
Normal file
BIN
proxy_docker/app/bin/lightning-cli_x86
Normal file
Binary file not shown.
3
proxy_docker/app/config/config.properties
Normal file
3
proxy_docker/app/config/config.properties
Normal file
@@ -0,0 +1,3 @@
|
||||
derivation.pub32=upub5GtUcgGed1aGH4HKQ3vMYrsmLXwmHhS1AeX33ZvDgZiyvkGhNTvGd2TA5Lr4v239Fzjj4ZY48t6wTtXUy2yRgapf37QHgt6KWEZ6bgsCLpb
|
||||
derivation.path=0/n
|
||||
watchingnode.pruned=false
|
||||
@@ -0,0 +1 @@
|
||||
user=rpc_username:rpc_password
|
||||
@@ -0,0 +1 @@
|
||||
user=rpc_username:rpc_password
|
||||
55
proxy_docker/app/data/watching.sql
Normal file
55
proxy_docker/app/data/watching.sql
Normal file
@@ -0,0 +1,55 @@
|
||||
PRAGMA foreign_keys = ON;
|
||||
|
||||
CREATE TABLE watching (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
address TEXT,
|
||||
watching INTEGER DEFAULT FALSE,
|
||||
callback0conf TEXT,
|
||||
calledback0conf INTEGER DEFAULT FALSE,
|
||||
callback1conf TEXT,
|
||||
calledback1conf INTEGER DEFAULT FALSE,
|
||||
imported INTEGER DEFAULT FALSE,
|
||||
inserted_ts INTEGER DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE INDEX idx_watching_address ON watching (address);
|
||||
|
||||
CREATE TABLE watching_tx (
|
||||
watching_id INTEGER REFERENCES watching,
|
||||
tx_id INTEGER REFERENCES tx,
|
||||
vout INTEGER,
|
||||
amount REAL
|
||||
);
|
||||
CREATE UNIQUE INDEX idx_watching_tx ON watching_tx (watching_id, tx_id);
|
||||
|
||||
CREATE TABLE tx (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
txid TEXT UNIQUE,
|
||||
hash TEXT UNIQUE,
|
||||
confirmations INTEGER DEFAULT 0,
|
||||
timereceived INTEGER,
|
||||
fee REAL,
|
||||
size INTEGER,
|
||||
vsize INTEGER,
|
||||
is_replaceable INTEGER,
|
||||
blockhash TEXT,
|
||||
blockheight INTEGER,
|
||||
blocktime INTEGER,
|
||||
raw_tx TEXT,
|
||||
inserted_ts INTEGER DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE INDEX idx_tx_timereceived ON tx (timereceived);
|
||||
CREATE INDEX idx_tx_fee ON tx (fee);
|
||||
CREATE INDEX idx_tx_size ON tx (size);
|
||||
CREATE INDEX idx_tx_vsize ON tx (vsize);
|
||||
CREATE INDEX idx_tx_blockhash ON tx (blockhash);
|
||||
CREATE INDEX idx_tx_blockheight ON tx (blockheight);
|
||||
CREATE INDEX idx_tx_blocktime ON tx (blocktime);
|
||||
|
||||
CREATE TABLE recipient (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
address TEXT,
|
||||
amount REAL,
|
||||
tx_id INTEGER REFERENCES tx,
|
||||
inserted_ts INTEGER DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE INDEX idx_recipient_address ON recipient (address);
|
||||
46
proxy_docker/app/script/bitcoin.sh
Normal file
46
proxy_docker/app/script/bitcoin.sh
Normal file
@@ -0,0 +1,46 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./trace.sh
|
||||
. ./utils.sh
|
||||
|
||||
deriveindex()
|
||||
{
|
||||
trace "Entering deriveindex()..."
|
||||
|
||||
local index=${1}
|
||||
trace "[deriveindex] index=${index}"
|
||||
|
||||
local pub32=$(get_prop "derivation.pub32")
|
||||
local path=$(get_prop "derivation.path" | sed -En "s/n/${index}/p")
|
||||
# pub32=$(grep "derivation.pub32" config.properties | cut -d'=' -f2)
|
||||
# path=$(grep "derivation.path" config.properties | cut -d'=' -f2 | sed -En "s/n/${index}/p")
|
||||
|
||||
local data="{\"pub32\":\"${pub32}\",\"path\":\"${path}\"}"
|
||||
trace "[deriveindex] data=${data}"
|
||||
|
||||
send_to_pycoin "${data}"
|
||||
return $?
|
||||
}
|
||||
|
||||
send_to_pycoin()
|
||||
{
|
||||
trace "Entering send_to_pycoin()..."
|
||||
|
||||
local data=${1}
|
||||
local result
|
||||
local returncode
|
||||
|
||||
trace "[send_to_pycoin] curl -s -H \"Content-Type: application/json\" -d \"${data}\" ${PYCOIN_CONTAINER}/derive"
|
||||
|
||||
result=$(curl -s -H "Content-Type: application/json" -d "${data}" ${PYCOIN_CONTAINER}/derive)
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
trace "[send_to_pycoin] result=${result}"
|
||||
|
||||
# Output response to stdout before exiting with return code
|
||||
echo "${result}"
|
||||
|
||||
trace_rc ${returncode}
|
||||
return ${returncode}
|
||||
|
||||
}
|
||||
79
proxy_docker/app/script/blockchainrpc.sh
Normal file
79
proxy_docker/app/script/blockchainrpc.sh
Normal file
@@ -0,0 +1,79 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./trace.sh
|
||||
. ./sendtobitcoinnode.sh
|
||||
|
||||
get_best_block_hash()
|
||||
{
|
||||
trace "Entering get_best_block_hash()..."
|
||||
|
||||
local data='{"method":"getbestblockhash"}'
|
||||
send_to_watcher_node "${data}"
|
||||
return $?
|
||||
}
|
||||
|
||||
getestimatesmartfee()
|
||||
{
|
||||
trace "Entering getestimatesmartfee()..."
|
||||
|
||||
local nb_blocks=${1}
|
||||
trace "[getestimatesmartfee] nb_blocks=${nb_blocks}"
|
||||
send_to_watcher_node "{\"method\":\"estimatesmartfee\",\"params\":[${nb_blocks}]}" | jq ".result.feerate" | awk '{ printf "%.8f", $0 }'
|
||||
return $?
|
||||
}
|
||||
|
||||
get_block_info()
|
||||
{
|
||||
trace "Entering get_block_info()..."
|
||||
|
||||
local block_hash=${1}
|
||||
trace "[get_block_info] block_hash=${block_hash}"
|
||||
local data="{\"method\":\"getblock\",\"params\":[\"${block_hash}\"]}"
|
||||
trace "[get_block_info] data=${data}"
|
||||
send_to_watcher_node "${data}"
|
||||
return $?
|
||||
}
|
||||
|
||||
get_best_block_info()
|
||||
{
|
||||
trace "Entering get_best_block_info()..."
|
||||
|
||||
local block_hash=$(echo "$(get_best_block_hash)" | jq ".result" | tr -d '"')
|
||||
trace "[get_best_block_info] block_hash=${block_hash}"
|
||||
get_block_info ${block_hash}
|
||||
return $?
|
||||
}
|
||||
|
||||
get_rawtransaction()
|
||||
{
|
||||
trace "Entering get_rawtransaction()..."
|
||||
|
||||
local txid=${1}
|
||||
trace "[get_rawtransaction] txid=${txid}"
|
||||
|
||||
local rawtx
|
||||
rawtx=$(sql "SELECT raw_tx FROM tx WHERE txid=\"${txid}\"")
|
||||
if [ -z ${rawtx} ]; then
|
||||
trace "[get_rawtransaction] rawtx not found in DB, let's fetch the Bitcoin node"
|
||||
local data="{\"method\":\"getrawtransaction\",\"params\":[\"${txid}\",true]}"
|
||||
trace "[get_rawtransaction] data=${data}"
|
||||
send_to_watcher_node "${data}"
|
||||
return $?
|
||||
else
|
||||
trace "[get_rawtransaction] rawtx found in DB, no need to fetch the Bitcoin node"
|
||||
echo ${rawtx}
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
get_transaction()
|
||||
{
|
||||
trace "Entering get_transaction()..."
|
||||
|
||||
local txid=${1}
|
||||
trace "[get_transaction] txid=${txid}"
|
||||
local data="{\"method\":\"gettransaction\",\"params\":[\"${txid}\",true]}"
|
||||
trace "[get_transaction] data=${data}"
|
||||
send_to_watcher_node "${data}"
|
||||
return $?
|
||||
}
|
||||
102
proxy_docker/app/script/call_lightningd.sh
Normal file
102
proxy_docker/app/script/call_lightningd.sh
Normal file
@@ -0,0 +1,102 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./trace.sh
|
||||
|
||||
ln_create_invoice()
|
||||
{
|
||||
trace "Entering ln_create_invoice()..."
|
||||
|
||||
local result
|
||||
|
||||
local request=${1}
|
||||
local msatoshi=$(echo "${request}" | jq ".msatoshi" | tr -d '"')
|
||||
trace "[ln_create_invoice] msatoshi=${msatoshi}"
|
||||
local label=$(echo "${request}" | jq ".label")
|
||||
trace "[ln_create_invoice] label=${label}"
|
||||
local description=$(echo "${request}" | jq ".description")
|
||||
trace "[ln_create_invoice] description=${description}"
|
||||
local expiry=$(echo "${request}" | jq ".expiry" | tr -d '"')
|
||||
trace "[ln_create_invoice] expiry=${expiry}"
|
||||
|
||||
result=$(./lightning-cli invoice ${msatoshi} "${label}" "${description}" ${expiry})
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
trace "[ln_create_invoice] result=${result}"
|
||||
|
||||
echo "${result}"
|
||||
|
||||
return ${returncode}
|
||||
}
|
||||
|
||||
ln_getinfo()
|
||||
{
|
||||
trace "Entering ln_get_info()..."
|
||||
|
||||
local result
|
||||
|
||||
result=$(./lightning-cli getinfo)
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
trace "[ln_getinfo] result=${result}"
|
||||
|
||||
echo "${result}"
|
||||
|
||||
return ${returncode}
|
||||
}
|
||||
|
||||
ln_pay() {
|
||||
trace "Entering ln_pay()..."
|
||||
|
||||
local result
|
||||
|
||||
local request=${1}
|
||||
local bolt11=$(echo "${request}" | jq ".bolt11" | tr -d '"')
|
||||
trace "[ln_pay] bolt11=${bolt11}"
|
||||
local expected_msatoshi=$(echo "${request}" | jq ".expected_msatoshi")
|
||||
trace "[ln_pay] expected_msatoshi=${expected_msatoshi}"
|
||||
local expected_description=$(echo "${request}" | jq ".expected_description")
|
||||
trace "[ln_pay] expected_description=${expected_description}"
|
||||
|
||||
result=$(./lightning-cli decodepay ${bolt11})
|
||||
|
||||
local invoice_msatoshi=$(echo "${result}" | jq ".msatoshi")
|
||||
trace "[ln_pay] invoice_msatoshi=${invoice_msatoshi}"
|
||||
local invoice_description=$(echo "${result}" | jq ".description")
|
||||
trace "[ln_pay] invoice_description=${invoice_description}"
|
||||
|
||||
if [ "${expected_msatoshi}" != "${invoice_msatoshi}" ]; then
|
||||
result="{\"result\":\"error\",\"expected_msatoshi\":${expected_msatoshi},\"invoice_msatoshi\":${invoice_msatoshi}}"
|
||||
returncode=1
|
||||
elif [ "${expected_description}" != "${invoice_description}" ]; then
|
||||
result="{\"result\":\"error\",\"expected_description\":${expected_description},\"invoice_description\":${invoice_description}}"
|
||||
returncode=1
|
||||
else
|
||||
result=$(./lightning-cli pay ${bolt11})
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
fi
|
||||
trace "[ln_pay] result=${result}"
|
||||
|
||||
echo "${result}"
|
||||
|
||||
return ${returncode}
|
||||
}
|
||||
|
||||
ln_newaddr()
|
||||
{
|
||||
trace "Entering ln_newaddr()..."
|
||||
|
||||
local result
|
||||
|
||||
call_lightningd newaddr
|
||||
result=$(./lightning-cli newaddr)
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
trace "[ln_newaddr] result=${result}"
|
||||
|
||||
echo "${result}"
|
||||
|
||||
return ${returncode}
|
||||
}
|
||||
|
||||
case "${0}" in *call_lightningd.sh) call_lightningd $@;; esac
|
||||
148
proxy_docker/app/script/callbacks_job.sh
Normal file
148
proxy_docker/app/script/callbacks_job.sh
Normal file
@@ -0,0 +1,148 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./trace.sh
|
||||
. ./sql.sh
|
||||
|
||||
do_callbacks()
|
||||
{
|
||||
(
|
||||
flock -x 200 || return 0
|
||||
|
||||
trace "Entering do_callbacks()..."
|
||||
|
||||
# Let's fetch all the watching addresses still being watched but not called back
|
||||
local callbacks=$(sql 'SELECT DISTINCT callback0conf, address, txid, vout, amount, confirmations, timereceived, fee, size, vsize, blockhash, blockheight, blocktime, w.id, is_replaceable FROM watching w LEFT JOIN watching_tx ON w.id = watching_id LEFT JOIN tx ON tx.id = tx_id WHERE NOT calledback0conf and watching_id NOT NULL and callback0conf NOT NULL and watching')
|
||||
trace "[do_callbacks] callbacks0conf=${callbacks}"
|
||||
|
||||
local returncode
|
||||
local address
|
||||
local IFS=$'\n'
|
||||
for row in ${callbacks}
|
||||
do
|
||||
build_callback ${row}
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
if [ "${returncode}" -eq 0 ]; then
|
||||
address=$(echo "${row}" | cut -d '|' -f2)
|
||||
sql "UPDATE watching SET calledback0conf=1 WHERE address=\"${address}\""
|
||||
trace_rc $?
|
||||
fi
|
||||
done
|
||||
|
||||
callbacks=$(sql 'SELECT DISTINCT callback1conf, address, txid, vout, amount, confirmations, timereceived, fee, size, vsize, blockhash, blockheight, blocktime, w.id, is_replaceable FROM watching w, watching_tx wt, tx t WHERE w.id = watching_id AND tx_id = t.id AND NOT calledback1conf and confirmations>0 and callback1conf NOT NULL and watching')
|
||||
trace "[do_callbacks] callbacks1conf=${callbacks}"
|
||||
|
||||
for row in ${callbacks}
|
||||
do
|
||||
build_callback ${row}
|
||||
returncode=$?
|
||||
if [ "${returncode}" -eq 0 ]; then
|
||||
address=$(echo "${row}" | cut -d '|' -f2)
|
||||
sql "UPDATE watching SET calledback1conf=1, watching=0 WHERE address=\"${address}\""
|
||||
trace_rc $?
|
||||
fi
|
||||
done
|
||||
) 200>./.callbacks.lock
|
||||
}
|
||||
|
||||
build_callback()
|
||||
{
|
||||
trace "Entering build_callback()..."
|
||||
|
||||
local row=$@
|
||||
local id
|
||||
local url
|
||||
local data
|
||||
local address
|
||||
local txid
|
||||
local vout_n
|
||||
local sent_amount
|
||||
local confirmations
|
||||
local ts_firstseen
|
||||
local fee
|
||||
local size
|
||||
local vsize
|
||||
local blockhash
|
||||
local blocktime
|
||||
local blockheight
|
||||
|
||||
# callback0conf, address, txid, vout, amount, confirmations, timereceived, fee, size, vsize, blockhash, blockheight, blocktime, w.id
|
||||
|
||||
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)
|
||||
trace "[build_callback] txid=${txid}"
|
||||
vout_n=$(echo "${row}" | cut -d '|' -f4)
|
||||
trace "[build_callback] vout_n=${vout_n}"
|
||||
sent_amount=$(echo "${row}" | cut -d '|' -f5)
|
||||
trace "[build_callback] sent_amount=${sent_amount}"
|
||||
confirmations=$(echo "${row}" | cut -d '|' -f6)
|
||||
trace "[build_callback] confirmations=${confirmations}"
|
||||
ts_firstseen=$(echo "${row}" | cut -d '|' -f7)
|
||||
trace "[build_callback] ts_firstseen=${ts_firstseen}"
|
||||
|
||||
# If node in pruned mode, we can't calculate the fees and then we don't want
|
||||
# to send 0.00000000 as fees but empty string to distinguish.
|
||||
fee=$(echo "${row}" | cut -d '|' -f8)
|
||||
if [ -n "${fee}" ]; then
|
||||
fee=$(echo "${fee}" | awk '{ printf "%.8f", $0 }')
|
||||
fi
|
||||
trace "[build_callback] fee=${fee}"
|
||||
size=$(echo "${row}" | cut -d '|' -f9)
|
||||
trace "[build_callback] size=${size}"
|
||||
vsize=$(echo "${row}" | cut -d '|' -f10)
|
||||
trace "[build_callback] vsize=${vsize}"
|
||||
is_replaceable=$(echo "${row}" | cut -d '|' -f15)
|
||||
trace "[build_callback] is_replaceable=${is_replaceable}"
|
||||
blockhash=$(echo "${row}" | cut -d '|' -f11)
|
||||
trace "[build_callback] blockhash=${blockhash}"
|
||||
blockheight=$(echo "${row}" | cut -d '|' -f12)
|
||||
trace "[build_callback] blockheight=${blockheight}"
|
||||
blocktime=$(echo "${row}" | cut -d '|' -f13)
|
||||
trace "[build_callback] blocktime=${blocktime}"
|
||||
|
||||
data="{\"id\":\"${id}\","
|
||||
data="${data}\"address\":\"${address}\","
|
||||
data="${data}\"hash\":\"${txid}\","
|
||||
data="${data}\"vout_n\":${vout_n},"
|
||||
data="${data}\"sent_amount\":${sent_amount},"
|
||||
data="${data}\"confirmations\":${confirmations},"
|
||||
data="${data}\"received\":\"$(date -Is -d @${ts_firstseen})\","
|
||||
data="${data}\"size\":${size},"
|
||||
data="${data}\"vsize\":${vsize},"
|
||||
data="${data}\"fees\":${fee},"
|
||||
data="${data}\"is_replaceable\":${is_replaceable},"
|
||||
data="${data}\"blockhash\":\"${blockhash}\","
|
||||
if [ -z ${blocktime} ]; then
|
||||
data="${data}\"blocktime\":\"\","
|
||||
else
|
||||
data="${data}\"blocktime\":\"$(date -Is -d @${blocktime})\","
|
||||
fi
|
||||
data="${data}\"blockheight\":\"${blockheight}\"}"
|
||||
trace "[build_callback] data=${data}"
|
||||
|
||||
curl_callback "${url}" "${data}"
|
||||
return $?
|
||||
}
|
||||
|
||||
curl_callback()
|
||||
{
|
||||
trace "Entering curl_callback()..."
|
||||
|
||||
local url=${1}
|
||||
local data=${2}
|
||||
|
||||
trace "[curl_callback] curl -H \"Content-Type: application/json\" -d \"${data}\" ${url}"
|
||||
curl -H "Content-Type: application/json" -H "X-Forwarded-Proto: https" -d "${data}" ${url}
|
||||
local returncode=$?
|
||||
trace_rc ${returncode}
|
||||
|
||||
return ${returncode}
|
||||
}
|
||||
|
||||
case "${0}" in *callbacks_job.sh) do_callbacks $@;; esac
|
||||
112
proxy_docker/app/script/computefees.sh
Normal file
112
proxy_docker/app/script/computefees.sh
Normal file
@@ -0,0 +1,112 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./trace.sh
|
||||
. ./sendtobitcoinnode.sh
|
||||
. ./sql.sh
|
||||
. ./blockchainrpc.sh
|
||||
|
||||
compute_fees()
|
||||
{
|
||||
local pruned=$(get_prop "watchingnode.pruned")
|
||||
if [ "${pruned}" = "true" ]; then
|
||||
trace "[compute_fees] pruned=${pruned}"
|
||||
# We want null instead of 0.00000000 in this case.
|
||||
echo "null"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
local txid=${1}
|
||||
|
||||
local tx_raw_details=$(cat rawtx-${txid}.blob)
|
||||
trace "[compute_fees] tx_raw_details=${tx_raw_details}"
|
||||
local vin_total_amount=$(compute_vin_total_amount "${tx_raw_details}")
|
||||
|
||||
local vout_total_amount=0
|
||||
local vout_value
|
||||
local vout_values=$(echo "${tx_raw_details}" | jq ".result.vout[].value")
|
||||
for vout_value in ${vout_values}
|
||||
do
|
||||
vout_total_amount=$(awk "BEGIN { printf(\"%.8f\", ${vout_total_amount}+${vout_value}); exit }")
|
||||
done
|
||||
|
||||
trace "[compute_fees] vin total amount=${vin_total_amount}"
|
||||
trace "[compute_fees] vout total amount=${vout_total_amount}"
|
||||
|
||||
local fees=$(awk "BEGIN { printf(\"%.8f\", ${vin_total_amount}-${vout_total_amount}); exit }")
|
||||
trace "[compute_fees] fees=${fees}"
|
||||
|
||||
echo ${fees}
|
||||
}
|
||||
|
||||
compute_vin_total_amount()
|
||||
{
|
||||
trace "Entering compute_vin_total_amount()..."
|
||||
|
||||
local main_tx=${1}
|
||||
# local vin_txids=$(echo ${main_tx} | jq '.result.vin[].txid')
|
||||
local vin_txids_vout=$(echo ${main_tx} | jq '.result.vin[] | ((.txid + "-") + (.vout | tostring))')
|
||||
trace "[compute_vin_total_amount] vin_txids_vout=${vin_txids_vout}"
|
||||
local returncode
|
||||
local vin_txid_vout
|
||||
local vin_txid
|
||||
local vin_raw_tx
|
||||
local vin_vout_amount=0
|
||||
local vout
|
||||
local vin_total_amount=0
|
||||
local vin_hash
|
||||
local vin_confirmations
|
||||
local vin_timereceived
|
||||
local vin_vsize
|
||||
local vin_blockhash
|
||||
local vin_blockheight
|
||||
local vin_blocktime
|
||||
local txid_already_inserted=true
|
||||
|
||||
for vin_txid_vout in ${vin_txids_vout}
|
||||
do
|
||||
# vin_txid=$(echo ${vin_txid} | tr -d '"')
|
||||
vin_txid=$(echo ${vin_txid_vout} | tr -d '"' | cut -d '-' -f1)
|
||||
# Check if we already have the tx in our DB
|
||||
vin_raw_tx=$(sql "SELECT raw_tx FROM tx WHERE txid=\"${vin_txid}\"")
|
||||
if [ -z "${vin_raw_tx}" ]; then
|
||||
txid_already_inserted=false
|
||||
vin_raw_tx=$(get_rawtransaction "${vin_txid}")
|
||||
returncode=$?
|
||||
if [ "${returncode}" -ne 0 ]; then
|
||||
return ${returncode}
|
||||
fi
|
||||
fi
|
||||
# vout=$(echo ${main_tx} | jq ".result.vin[] | select(.txid == \"${vin_txid}\") | .vout")
|
||||
vout=$(echo ${vin_txid_vout} | tr -d '"' | cut -d '-' -f2)
|
||||
trace "[compute_vin_total_amount] vout=${vout}"
|
||||
vin_vout_amount=$(echo ${vin_raw_tx} | jq ".result.vout[] | select(.n == ${vout}) | .value" | awk '{ printf "%.8f", $0 }')
|
||||
trace "[compute_vin_total_amount] vin_vout_amount=${vin_vout_amount}"
|
||||
vin_total_amount=$(awk "BEGIN { printf(\"%.8f\", ${vin_total_amount}+${vin_vout_amount}); exit}")
|
||||
trace "[compute_vin_total_amount] vin_total_amount=${vin_total_amount}"
|
||||
vin_hash=$(echo ${vin_raw_tx} | jq ".result.hash")
|
||||
vin_confirmations=$(echo ${vin_raw_tx} | jq ".result.confirmations")
|
||||
vin_timereceived=$(echo ${vin_raw_tx} | jq ".result.time")
|
||||
vin_size=$(echo ${vin_raw_tx} | jq ".result.size")
|
||||
vin_vsize=$(echo ${vin_raw_tx} | jq ".result.vsize")
|
||||
vin_blockhash=$(echo ${vin_raw_tx} | jq ".result.blockhash")
|
||||
vin_blockheight=$(echo ${vin_raw_tx} | jq ".result.blockheight")
|
||||
vin_blocktime=$(echo ${vin_raw_tx} | jq ".result.blocktime")
|
||||
|
||||
# Let's insert the vin tx in the DB just in case it would be useful
|
||||
if ! ${txid_already_inserted}; then
|
||||
# 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 "${vin_raw_tx}" > rawtx-${vin_txid}.blob
|
||||
sql "INSERT OR IGNORE INTO tx (txid, hash, confirmations, timereceived, size, vsize, blockhash, blockheight, blocktime, raw_tx) VALUES (\"${vin_txid}\", ${vin_hash}, ${vin_confirmations}, ${vin_timereceived}, ${vin_size}, ${vin_vsize}, ${vin_blockhash}, ${vin_blockheight}, ${vin_blocktime}, readfile('rawtx-${vin_txid}.blob'))"
|
||||
trace_rc $?
|
||||
rm rawtx-${vin_txid}.blob
|
||||
txid_already_inserted=true
|
||||
fi
|
||||
done
|
||||
|
||||
echo ${vin_total_amount}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
case "${0}" in *computefees.sh) compute_vin_total_amount $@;; esac
|
||||
150
proxy_docker/app/script/confirmation.sh
Normal file
150
proxy_docker/app/script/confirmation.sh
Normal file
@@ -0,0 +1,150 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./trace.sh
|
||||
. ./sql.sh
|
||||
. ./callbacks_job.sh
|
||||
. ./sendtobitcoinnode.sh
|
||||
. ./responsetoclient.sh
|
||||
. ./computefees.sh
|
||||
. ./blockchainrpc.sh
|
||||
|
||||
confirmation_request()
|
||||
{
|
||||
# We are receiving a HTTP request, let's find the TXID from it
|
||||
|
||||
trace "Entering confirmation_request()..."
|
||||
|
||||
local request=${1}
|
||||
local txid=$(echo "${request}" | cut -d ' ' -f2 | cut -d '/' -f3)
|
||||
|
||||
confirmation "${txid}"
|
||||
return $?
|
||||
}
|
||||
|
||||
confirmation()
|
||||
{
|
||||
trace "Entering confirmation()..."
|
||||
|
||||
local txid=${1}
|
||||
local tx_details=$(get_transaction ${txid})
|
||||
|
||||
########################################################################################################
|
||||
# First of all, let's make sure we're working on watched addresses...
|
||||
local address
|
||||
local addresseswhere
|
||||
local addresses=$(echo ${tx_details} | jq ".result.details[].address")
|
||||
|
||||
local notfirst=false
|
||||
local IFS=$'\n'
|
||||
for address in ${addresses}
|
||||
do
|
||||
trace "[confirmation] address=${address}"
|
||||
|
||||
if ${notfirst}; then
|
||||
addresseswhere="${addresseswhere},${address}"
|
||||
else
|
||||
addresseswhere="${address}"
|
||||
notfirst=true
|
||||
fi
|
||||
done
|
||||
local rows=$(sql "SELECT id, address FROM watching WHERE address IN (${addresseswhere}) AND watching")
|
||||
if [ ${#rows} -eq 0 ]; then
|
||||
trace "[confirmation] No watched address in this tx!"
|
||||
return 0
|
||||
fi
|
||||
########################################################################################################
|
||||
|
||||
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')
|
||||
|
||||
# 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
|
||||
|
||||
if [ -z ${tx} ]; then
|
||||
# TX not found in our DB.
|
||||
# 0-conf or missed conf (managed or while spending) or spending an unconfirmed
|
||||
# (note: spending an unconfirmed TX must be avoided or we'll get here spending an unprocessed watching)
|
||||
|
||||
# Let's first insert the tx in our DB
|
||||
|
||||
local tx_hash=$(echo ${tx_raw_details} | jq '.result.hash')
|
||||
local tx_ts_firstseen=$(echo ${tx_details} | jq '.result.timereceived')
|
||||
local tx_amount=$(echo ${tx_details} | jq '.result.amount')
|
||||
|
||||
local tx_size=$(echo ${tx_raw_details} | jq '.result.size')
|
||||
local tx_vsize=$(echo ${tx_raw_details} | jq '.result.vsize')
|
||||
local tx_replaceable=$(echo ${tx_details} | jq '.result."bip125-replaceable"')
|
||||
tx_replaceable=$([ ${tx_replaceable} = "yes" ] && echo 1 || echo 0)
|
||||
|
||||
local fees=$(compute_fees "${txid}")
|
||||
trace "[confirmation] fees=${fees}"
|
||||
|
||||
# If we missed 0-conf...
|
||||
local tx_blockhash=$(echo ${tx_details} | jq '.result.blockhash')
|
||||
local tx_blockheight=$(echo ${tx_details} | jq '.result.blockheight')
|
||||
local tx_blocktime=$(echo ${tx_details} | jq '.result.blocktime')
|
||||
|
||||
sql "INSERT OR IGNORE INTO tx (txid, hash, confirmations, timereceived, fee, size, vsize, is_replaceable, blockhash, blockheight, blocktime, raw_tx) VALUES (\"${txid}\", ${tx_hash}, ${tx_nb_conf}, ${tx_ts_firstseen}, ${fees}, ${tx_size}, ${tx_vsize}, ${tx_replaceable}, ${tx_blockhash}, ${tx_blockheight}, ${tx_blocktime}, readfile('rawtx-${txid}.blob'))"
|
||||
trace_rc $?
|
||||
|
||||
id_inserted=$(sql "SELECT id FROM tx WHERE txid='${txid}'")
|
||||
trace_rc $?
|
||||
|
||||
else
|
||||
# TX found in our DB.
|
||||
# 1-conf or spending watched address (in this case, we probably missed conf)
|
||||
|
||||
local tx_blockhash=$(echo ${tx_details} | jq '.result.blockhash')
|
||||
local tx_blockheight=$(echo ${tx_details} | jq '.result.blockheight')
|
||||
local tx_blocktime=$(echo ${tx_details} | jq '.result.blocktime')
|
||||
|
||||
sql "UPDATE tx SET
|
||||
confirmations=${tx_nb_conf},
|
||||
blockhash=${tx_blockhash},
|
||||
blockheight=${tx_blockheight},
|
||||
blocktime=${tx_blocktime},
|
||||
raw_tx=readfile('rawtx-${txid}.blob')
|
||||
WHERE txid=\"${txid}\""
|
||||
trace_rc $?
|
||||
|
||||
id_inserted=${tx}
|
||||
|
||||
fi
|
||||
# Delete the temp file containing the raw tx (see above)
|
||||
rm rawtx-${txid}.blob
|
||||
|
||||
########################################################################################################
|
||||
# Let's now insert in the join table if not already done
|
||||
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"
|
||||
local watching_id
|
||||
|
||||
# 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
|
||||
watching_id=$(echo "${row}" | cut -d '|' -f1)
|
||||
address=$(echo "${row}" | cut -d '|' -f2)
|
||||
tx_vout_n=$(echo ${tx_details} | jq ".result.details[] | select(.address==\"${address}\") | .vout")
|
||||
tx_vout_amount=$(echo ${tx_details} | jq ".result.details[] | select(.address==\"${address}\") | .amount")
|
||||
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
|
||||
########################################################################################################
|
||||
|
||||
do_callbacks
|
||||
|
||||
echo '{"result":"confirmed"}'
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
case "${0}" in *confirmation.sh) confirmation $@;; esac
|
||||
64
proxy_docker/app/script/getactivewatches.sh
Normal file
64
proxy_docker/app/script/getactivewatches.sh
Normal file
@@ -0,0 +1,64 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./trace.sh
|
||||
. ./sql.sh
|
||||
|
||||
getactivewatches()
|
||||
{
|
||||
trace "Entering getactivewatches()..."
|
||||
|
||||
local watches
|
||||
watches=$(sql "SELECT id, address, imported, callback0conf, callback1conf, inserted_ts FROM watching WHERE watching AND NOT calledback1conf")
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
|
||||
local id
|
||||
local address
|
||||
local imported
|
||||
local inserted
|
||||
local cb0conf_url
|
||||
local cb1conf_url
|
||||
local timestamp
|
||||
local notfirst=false
|
||||
|
||||
echo -n "{\"watches\":["
|
||||
|
||||
local IFS=$'\n'
|
||||
for row in ${watches}
|
||||
do
|
||||
if ${notfirst}; then
|
||||
echo ","
|
||||
else
|
||||
notfirst=true
|
||||
fi
|
||||
trace "[getactivewatches] row=${row}"
|
||||
id=$(echo "${row}" | cut -d '|' -f1)
|
||||
trace "[getactivewatches] id=${id}"
|
||||
address=$(echo "${row}" | cut -d '|' -f2)
|
||||
trace "[getactivewatches] address=${address}"
|
||||
imported=$(echo "${row}" | cut -d '|' -f3)
|
||||
trace "[getactivewatches] imported=${imported}"
|
||||
cb0conf_url=$(echo "${row}" | cut -d '|' -f4)
|
||||
trace "[getactivewatches] cb0conf_url=${cb0conf_url}"
|
||||
cb1conf_url=$(echo "${row}" | cut -d '|' -f5)
|
||||
trace "[getactivewatches] cb1conf_url=${cb1conf_url}"
|
||||
timestamp=$(echo "${row}" | cut -d '|' -f6)
|
||||
trace "[getactivewatches] timestamp=${timestamp}"
|
||||
|
||||
data="{\"id\":\"${id}\","
|
||||
data="${data}\"address\":\"${address}\","
|
||||
data="${data}\"imported\":\"${imported}\","
|
||||
data="${data}\"unconfirmedCallbackURL\":\"${cb0conf_url}\","
|
||||
data="${data}\"confirmedCallbackURL\":\"${cb1conf_url}\","
|
||||
data="${data}\"watching_since\":\"${timestamp}\"}"
|
||||
trace "[getactivewatches] data=${data}"
|
||||
|
||||
echo -n "${data}"
|
||||
done
|
||||
|
||||
echo "]}"
|
||||
|
||||
return ${returncode}
|
||||
}
|
||||
|
||||
case "${0}" in *getactivewatches.sh) getactivewatches;; esac
|
||||
21
proxy_docker/app/script/importaddress.sh
Normal file
21
proxy_docker/app/script/importaddress.sh
Normal file
@@ -0,0 +1,21 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./trace.sh
|
||||
. ./sendtobitcoinnode.sh
|
||||
|
||||
importaddress_rpc()
|
||||
{
|
||||
trace "[Entering importaddress_rpc()]"
|
||||
|
||||
local address=${1}
|
||||
local data="{\"method\":\"importaddress\",\"params\":[\"${address}\",\"\",false]}"
|
||||
local result
|
||||
result=$(send_to_watcher_node ${data})
|
||||
local returncode=$?
|
||||
|
||||
echo "${result}"
|
||||
|
||||
return ${returncode}
|
||||
}
|
||||
|
||||
case "${0}" in *importaddress.sh) importaddress_rpc $@;; esac
|
||||
86
proxy_docker/app/script/manage_missed_conf.sh
Normal file
86
proxy_docker/app/script/manage_missed_conf.sh
Normal file
@@ -0,0 +1,86 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./trace.sh
|
||||
. ./sql.sh
|
||||
. ./importaddress.sh
|
||||
. ./confirmation.sh
|
||||
|
||||
manage_not_imported()
|
||||
{
|
||||
# When we tried to import watched addresses in the watching node,
|
||||
# if it didn't succeed, we try again here.
|
||||
|
||||
trace "[Entering manage_not_imported()]"
|
||||
|
||||
local watches=$(sql 'SELECT address FROM watching WHERE watching AND NOT imported')
|
||||
trace "[manage_not_imported] watches=${watches}"
|
||||
|
||||
local result
|
||||
local returncode
|
||||
local IFS=$'\n'
|
||||
for address in ${watches}
|
||||
do
|
||||
result=$(importaddress_rpc "${address}")
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
if [ "${returncode}" -eq 0 ]; then
|
||||
sql "UPDATE watching SET imported=1 WHERE address=\"${address}\""
|
||||
fi
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
manage_missed_conf()
|
||||
{
|
||||
# Maybe we missed confirmations, because we were down or no network or
|
||||
# whatever, so we look at what might be missed and do confirmations.
|
||||
|
||||
trace "[Entering manage_missed_conf()]"
|
||||
|
||||
# local watches=$(sql 'SELECT address FROM watching WHERE txid IS NULL AND watching AND imported')
|
||||
#local watches=$(sql 'SELECT address FROM watching LEFT JOIN watching_tx ON id = watching_id WHERE watching AND imported AND tx_id IS NULL')
|
||||
local watches=$(sql 'SELECT address FROM watching w LEFT JOIN watching_tx ON w.id = watching_id LEFT JOIN tx t ON t.id = tx_id WHERE watching AND imported AND (tx_id IS NULL OR t.confirmations=0)')
|
||||
trace "[manage_missed_conf] watches=${watches}"
|
||||
if [ ${#watches} -eq 0 ]; then
|
||||
trace "[manage_missed_conf] Nothing missed!"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local addresses
|
||||
local data
|
||||
local result
|
||||
local returncode
|
||||
local IFS=$'\n'
|
||||
for address in ${watches}
|
||||
do
|
||||
if [ -z ${addresses} ]; then
|
||||
addresses="[\"${address}\""
|
||||
else
|
||||
addresses="${addresses},\"${address}\""
|
||||
fi
|
||||
done
|
||||
addresses="${addresses}]"
|
||||
|
||||
# Watching addresses with UTXO are transactions being watched that went through without us knowing it, we missed the conf
|
||||
data="{\"method\":\"listunspent\",\"params\":[0, 9999999, ${addresses}]}"
|
||||
local unspents
|
||||
unspents=$(send_to_watcher_node ${data})
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
if [ "${returncode}" -ne 0 ]; then
|
||||
return ${returncode}
|
||||
fi
|
||||
|
||||
# | tr -d '"'
|
||||
local txids=$(echo ${unspents} | jq ".result[].txid" | tr -d '"')
|
||||
for txid in ${txids}
|
||||
do
|
||||
confirmation "${txid}"
|
||||
done
|
||||
|
||||
return 0
|
||||
|
||||
}
|
||||
|
||||
case "${0}" in *manage_missed_conf.sh) manage_not_imported $@; manage_missed_conf $@;; esac
|
||||
238
proxy_docker/app/script/requesthandler.sh
Normal file
238
proxy_docker/app/script/requesthandler.sh
Normal file
@@ -0,0 +1,238 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
. ./sendtobitcoinnode.sh
|
||||
. ./callbacks_job.sh
|
||||
. ./watchrequest.sh
|
||||
. ./unwatchrequest.sh
|
||||
. ./getactivewatches.sh
|
||||
. ./confirmation.sh
|
||||
. ./blockchainrpc.sh
|
||||
. ./responsetoclient.sh
|
||||
. ./trace.sh
|
||||
. ./manage_missed_conf.sh
|
||||
. ./walletoperations.sh
|
||||
. ./bitcoin.sh
|
||||
. ./call_lightningd.sh
|
||||
|
||||
main()
|
||||
{
|
||||
trace "Entering main()..."
|
||||
|
||||
local step=0
|
||||
local cmd
|
||||
local http_method
|
||||
local line
|
||||
local content_length
|
||||
local response
|
||||
local returncode
|
||||
|
||||
while read line; do
|
||||
line=$(echo "${line}" | tr -d '\r\n')
|
||||
trace "[main] line=${line}"
|
||||
|
||||
if [ "${cmd}" = "" ]; then
|
||||
# First line!
|
||||
# Looking for something like:
|
||||
# GET /cmd/params HTTP/1.1
|
||||
# POST / HTTP/1.1
|
||||
cmd=$(echo "${line}" | cut -d '/' -f2 | cut -d ' ' -f1)
|
||||
trace "[main] cmd=${cmd}"
|
||||
http_method=$(echo "${line}" | cut -d ' ' -f1)
|
||||
trace "[main] http_method=${http_method}"
|
||||
if [ "${http_method}" = "GET" ]; then
|
||||
step=1
|
||||
fi
|
||||
fi
|
||||
if [ "${line}" = "" ]; then
|
||||
trace "[main] empty line"
|
||||
if [ ${step} -eq 1 ]; then
|
||||
trace "[main] body part finished, disconnecting"
|
||||
break
|
||||
else
|
||||
trace "[main] headers part finished, body incoming"
|
||||
step=1
|
||||
fi
|
||||
fi
|
||||
# line=content-length: 406
|
||||
case "${line}" in *[cC][oO][nN][tT][eE][nN][tT]-[lL][eE][nN][gG][tT][hH]*)
|
||||
content_length=$(echo ${line} | cut -d ':' -f2)
|
||||
trace "[main] content_length=${content_length}";
|
||||
;;
|
||||
esac
|
||||
if [ ${step} -eq 1 ]; then
|
||||
trace "[main] step=${step}"
|
||||
if [ "${http_method}" = "POST" ]; then
|
||||
read -n ${content_length} line
|
||||
trace "[main] line=${line}"
|
||||
fi
|
||||
case "${cmd}" in
|
||||
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"}
|
||||
|
||||
response=$(watchrequest "${line}")
|
||||
response_to_client "${response}" ${?}
|
||||
break
|
||||
;;
|
||||
unwatch)
|
||||
# curl (GET) 192.168.111.152:8080/unwatch/2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp
|
||||
|
||||
response=$(unwatchrequest "${line}")
|
||||
response_to_client "${response}" ${?}
|
||||
break
|
||||
;;
|
||||
getactivewatches)
|
||||
# curl (GET) 192.168.111.152:8080/getactivewatches
|
||||
|
||||
response=$(getactivewatches)
|
||||
response_to_client "${response}" ${?}
|
||||
break
|
||||
;;
|
||||
conf)
|
||||
# curl (GET) 192.168.111.152:8080/conf/b081ca7724386f549cf0c16f71db6affeb52ff7a0d9b606fb2e5c43faffd3387
|
||||
|
||||
response=$(confirmation_request "${line}")
|
||||
response_to_client "${response}" ${?}
|
||||
break
|
||||
;;
|
||||
getbestblockhash)
|
||||
# curl (GET) http://192.168.111.152:8080/getbestblockhash
|
||||
|
||||
response=$(get_best_block_hash)
|
||||
response_to_client "${response}" ${?}
|
||||
break
|
||||
;;
|
||||
getblockinfo)
|
||||
# curl (GET) http://192.168.111.152:8080/getblockinfo/000000006f82a384c208ecfa04d05beea02d420f3f398ddda5c7f900de5718ea
|
||||
|
||||
response=$(get_block_info $(echo "${line}" | cut -d ' ' -f2 | cut -d '/' -f3))
|
||||
response_to_client "${response}" ${?}
|
||||
break
|
||||
;;
|
||||
gettransaction)
|
||||
# curl (GET) http://192.168.111.152:8080/gettransaction/af867c86000da76df7ddb1054b273ca9e034e8c89d049b5b2795f9f590f67648
|
||||
|
||||
response=$(get_rawtransaction $(echo "${line}" | cut -d ' ' -f2 | cut -d '/' -f3))
|
||||
response_to_client "${response}" ${?}
|
||||
break
|
||||
;;
|
||||
getbestblockinfo)
|
||||
# curl (GET) http://192.168.111.152:8080/getbestblockinfo
|
||||
|
||||
response=$(get_best_block_info)
|
||||
response_to_client "${response}" ${?}
|
||||
break
|
||||
;;
|
||||
executecallbacks)
|
||||
# curl (GET) http://192.168.111.152:8080/executecallbacks
|
||||
|
||||
manage_not_imported
|
||||
manage_missed_conf
|
||||
response=$(do_callbacks)
|
||||
response_to_client "${response}" ${?}
|
||||
break
|
||||
;;
|
||||
getbalance)
|
||||
# curl (GET) http://192.168.111.152:8080/getbalance
|
||||
|
||||
response=$(getbalance)
|
||||
response_to_client "${response}" ${?}
|
||||
break
|
||||
;;
|
||||
getnewaddress)
|
||||
# curl (GET) http://192.168.111.152:8080/getnewaddress
|
||||
|
||||
response=$(getnewaddress)
|
||||
response_to_client "${response}" ${?}
|
||||
break
|
||||
;;
|
||||
spend)
|
||||
# POST http://192.168.111.152:8080/spend
|
||||
# BODY {"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","amount":0.00233}
|
||||
|
||||
response=$(spend "${line}")
|
||||
response_to_client "${response}" ${?}
|
||||
break
|
||||
;;
|
||||
addtobatch)
|
||||
# POST http://192.168.111.152:8080/addtobatch
|
||||
# BODY {"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","amount":0.00233}
|
||||
|
||||
response=$(addtobatching $(echo "${line}" | jq ".address" | tr -d '"') $(echo "${line}" | jq ".amount"))
|
||||
response_to_client "${response}" ${?}
|
||||
break
|
||||
;;
|
||||
batchspend)
|
||||
# GET http://192.168.111.152:8080/batchspend
|
||||
|
||||
response=$(batchspend "${line}")
|
||||
response_to_client "${response}" ${?}
|
||||
break
|
||||
;;
|
||||
deriveindex)
|
||||
# curl GET http://192.168.111.152:8080/deriveindex/25-30
|
||||
# curl GET http://192.168.111.152:8080/deriveindex/34
|
||||
|
||||
response=$(deriveindex $(echo "${line}" | cut -d ' ' -f2 | cut -d '/' -f3))
|
||||
response_to_client "${response}" ${?}
|
||||
break
|
||||
;;
|
||||
derivepubpath)
|
||||
# POST http://192.168.111.152:8080/derivepubpath
|
||||
# BODY {"pub32":"tpubD6NzVbkrYhZ4YR3QK2tyfMMvBghAvqtNaNK1LTyDWcRHLcMUm3ZN2cGm5BS3MhCRCeCkXQkTXXjiJgqxpqXK7PeUSp86DTTgkLpcjMtpKWk","path":"0/25-30"}
|
||||
# BODY {"pub32":"upub5GtUcgGed1aGH4HKQ3vMYrsmLXwmHhS1AeX33ZvDgZiyvkGhNTvGd2TA5Lr4v239Fzjj4ZY48t6wTtXUy2yRgapf37QHgt6KWEZ6bgsCLpb","path":"0/25-30"}
|
||||
# BODY {"pub32":"vpub5SLqN2bLY4WeZF3kL4VqiWF1itbf3A6oRrq9aPf16AZMVWYCuN9TxpAZwCzVgW94TNzZPNc9XAHD4As6pdnExBtCDGYRmNJrcJ4eV9hNqcv","path":"0/25-30"}
|
||||
|
||||
response=$(send_to_pycoin "${line}")
|
||||
response_to_client "${response}" ${?}
|
||||
break
|
||||
;;
|
||||
ln_getinfo)
|
||||
# GET http://192.168.111.152:8080/ln_getinfo
|
||||
|
||||
response=$(ln_getinfo)
|
||||
response_to_client "${response}" ${?}
|
||||
break
|
||||
;;
|
||||
ln_create_invoice)
|
||||
# POST http://192.168.111.152:8080/ln_create_invoice
|
||||
# BODY {"msatoshi":"10000","label":"koNCcrSvhX3dmyFhW","description":"Bylls order #10649","expiry":"900"}
|
||||
|
||||
response=$(ln_create_invoice "${line}")
|
||||
response_to_client "${response}" ${?}
|
||||
break
|
||||
;;
|
||||
ln_pay)
|
||||
# POST http://192.168.111.152:8080/ln_pay
|
||||
# BODY {"bolt11":"lntb1pdca82tpp5gv8mn5jqlj6xztpnt4r472zcyrwf3y2c3cvm4uzg2gqcnj90f83qdp2gf5hgcm0d9hzqnm4w3kx2apqdaexgetjyq3nwvpcxgcqp2g3d86wwdfvyxcz7kce7d3n26d2rw3wf5tzpm2m5fl2z3mm8msa3xk8nv2y32gmzlhwjved980mcmkgq83u9wafq9n4w28amnmwzujgqpmapcr3","expected_msatoshi":"10000","expected_description":"Bitcoin Outlet order #7082"}
|
||||
|
||||
response=$(ln_pay "${line}")
|
||||
response_to_client "${response}" ${?}
|
||||
break
|
||||
;;
|
||||
ln_newaddr)
|
||||
# GET http://192.168.111.152:8080/ln_newaddr
|
||||
|
||||
response=$(ln_newaddr)
|
||||
response_to_client "${response}" ${?}
|
||||
break
|
||||
;;
|
||||
esac
|
||||
break
|
||||
fi
|
||||
done
|
||||
trace "[main] exiting"
|
||||
return 0
|
||||
}
|
||||
|
||||
export NODE_RPC_URL=$BTC_NODE_RPC_URL
|
||||
export TRACING
|
||||
export DB_PATH
|
||||
export DB_FILE
|
||||
|
||||
main
|
||||
exit $?
|
||||
21
proxy_docker/app/script/responsetoclient.sh
Normal file
21
proxy_docker/app/script/responsetoclient.sh
Normal file
@@ -0,0 +1,21 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./trace.sh
|
||||
|
||||
response_to_client()
|
||||
{
|
||||
trace "Entering response_to_client()..."
|
||||
|
||||
local response=${1}
|
||||
local returncode=${2}
|
||||
|
||||
([ -z "${returncode}" ] || [ "${returncode}" -eq "0" ]) && echo -ne "HTTP/1.1 200 OK\r\n"
|
||||
[ -n "${returncode}" ] && [ "${returncode}" -ne "0" ] && echo -ne "HTTP/1.1 400 Bad Request\r\n"
|
||||
|
||||
echo -e "Content-Type: application/json\r\nContent-Length: ${#response}\r\n\r\n${response}"
|
||||
|
||||
# Small delay needed for the data to be processed correctly by peer
|
||||
sleep 0.2s
|
||||
}
|
||||
|
||||
case "${0}" in *responsetoclient.sh) response_to_client $@;; esac
|
||||
59
proxy_docker/app/script/sendtobitcoinnode.sh
Normal file
59
proxy_docker/app/script/sendtobitcoinnode.sh
Normal file
@@ -0,0 +1,59 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./trace.sh
|
||||
|
||||
send_to_watcher_node()
|
||||
{
|
||||
trace "Entering send_to_watcher_node()..."
|
||||
send_to_bitcoin_node ${WATCHER_NODE_RPC_URL} watcher_btcnode_curlcfg.properties $@
|
||||
local returncode=$?
|
||||
trace_rc ${returncode}
|
||||
return ${returncode}
|
||||
}
|
||||
|
||||
send_to_spender_node()
|
||||
{
|
||||
trace "Entering send_to_spender_node()..."
|
||||
send_to_bitcoin_node ${SPENDER_NODE_RPC_URL} spender_btcnode_curlcfg.properties $@
|
||||
local returncode=$?
|
||||
trace_rc ${returncode}
|
||||
return ${returncode}
|
||||
}
|
||||
|
||||
send_to_bitcoin_node()
|
||||
{
|
||||
trace "Entering send_to_bitcoin_node()..."
|
||||
local returncode
|
||||
local result
|
||||
local errorstring
|
||||
local node_url=${1}
|
||||
local configfile=${2}
|
||||
local data=${3}
|
||||
|
||||
trace "[send_to_bitcoin_node] curl -s --config ${configfile} -H \"Content-Type: application/json\" -d \"${data}\" ${node_url}"
|
||||
result=$(curl -s --config ${configfile} -H "Content-Type: application/json" -d "${data}" ${node_url})
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
trace "[send_to_bitcoin_node] result=${result}"
|
||||
|
||||
if [ "${returncode}" -eq 0 ]; then
|
||||
# Node responded, let's see if we got an error message from the node
|
||||
# jq -e will have a return code of 1 if the supplied tag is null.
|
||||
errorstring=$(echo "${result}" | jq -e ".error")
|
||||
if [ "$?" -eq "0" ]; then
|
||||
# Error tag not null, so there's an error
|
||||
trace "[send_to_bitcoin_node] Node responded, error found in response: ${errorstring}"
|
||||
returncode=1
|
||||
else
|
||||
trace "[send_to_bitcoin_node] Node responded, no error found in response, yayy!"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Output response to stdout before exiting with return code
|
||||
echo "${result}"
|
||||
|
||||
trace_rc ${returncode}
|
||||
return ${returncode}
|
||||
}
|
||||
|
||||
case "${0}" in *sendtobitcoinnode.sh) send_to_bitcoin_node $@;; esac
|
||||
12
proxy_docker/app/script/sql.sh
Normal file
12
proxy_docker/app/script/sql.sh
Normal file
@@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./trace.sh
|
||||
|
||||
sql()
|
||||
{
|
||||
trace "sqlite3 ${DB_FILE} '${1}'"
|
||||
sqlite3 -cmd ".timeout 20000" ${DB_FILE} "${1}"
|
||||
return $?
|
||||
}
|
||||
|
||||
case "${0}" in *sql.sh) sql $@;; esac
|
||||
15
proxy_docker/app/script/startproxy.sh
Normal file
15
proxy_docker/app/script/startproxy.sh
Normal file
@@ -0,0 +1,15 @@
|
||||
#!/bin/sh
|
||||
|
||||
export PROXY_LISTENING_PORT
|
||||
export WATCHER_NODE_RPC_URL=$WATCHER_BTC_NODE_RPC_URL
|
||||
export SPENDER_NODE_RPC_URL=$SPENDER_BTC_NODE_RPC_URL
|
||||
export TRACING
|
||||
export DB_PATH
|
||||
export DB_FILE
|
||||
|
||||
if [ ! -e ${DB_FILE} ]; then
|
||||
echo "DB not found, creating..." > /dev/stderr
|
||||
cat watching.sql | sqlite3 $DB_FILE
|
||||
fi
|
||||
|
||||
nc -vlkp${PROXY_LISTENING_PORT} -e ./requesthandler.sh
|
||||
9
proxy_docker/app/script/tests-cb.sh
Normal file
9
proxy_docker/app/script/tests-cb.sh
Normal file
@@ -0,0 +1,9 @@
|
||||
#!/bin/sh
|
||||
|
||||
read line
|
||||
echo ${line} > /dev/stderr
|
||||
echo -ne "HTTP/1.1 200 OK\r\n"
|
||||
echo -e "Content-Type: application/json\r\nContent-Length: 0\r\n\r\n"
|
||||
|
||||
# Small delay needed for the data to be processed correctly by peer
|
||||
sleep 0.5s
|
||||
322
proxy_docker/app/script/tests.sh
Normal file
322
proxy_docker/app/script/tests.sh
Normal file
@@ -0,0 +1,322 @@
|
||||
#!/bin/sh
|
||||
|
||||
tests()
|
||||
{
|
||||
local address
|
||||
local address1
|
||||
local address2
|
||||
local address3
|
||||
local response
|
||||
|
||||
# getbestblockhash
|
||||
# (GET) http://cyphernode:8888/getbestblockhash
|
||||
|
||||
echo "Testing getbestblockhash..."
|
||||
response=$(curl -s cyphernode:8888/getbestblockhash)
|
||||
echo "response=${response}"
|
||||
local blockhash=$(echo ${response} | jq ".result" | tr -d '\"')
|
||||
echo "blockhash=${blockhash}"
|
||||
if [ -z "${blockhash}" ]; then
|
||||
exit 2
|
||||
fi
|
||||
echo "Tested getbestblockhash."
|
||||
|
||||
# getbestblockinfo
|
||||
# curl (GET) http://cyphernode:8888/getbestblockinfo
|
||||
|
||||
echo "Testing getbestblockinfo..."
|
||||
response=$(curl -s cyphernode:8888/getbestblockinfo)
|
||||
echo "response=${response}"
|
||||
local blockhash2=$(echo ${response} | jq ".result.hash" | tr -d '\"')
|
||||
echo "blockhash2=${blockhash2}"
|
||||
if [ "${blockhash2}" != "${blockhash}" ]; then
|
||||
exit 4
|
||||
fi
|
||||
echo "Tested getbestblockinfo."
|
||||
|
||||
# getblockinfo
|
||||
# (GET) http://cyphernode:8888/getblockinfo/000000006f82a384c208ecfa04d05beea02d420f3f398ddda5c7f900de5718ea
|
||||
|
||||
echo "Testing getblockinfo..."
|
||||
response=$(curl -s cyphernode:8888/getblockinfo/${blockhash})
|
||||
echo "response=${response}"
|
||||
blockhash2=$(echo ${response} | jq ".result.hash" | tr -d '\"')
|
||||
echo "blockhash2=${blockhash2}"
|
||||
if [ "${blockhash2}" != "${blockhash}" ]; then
|
||||
exit 6
|
||||
fi
|
||||
echo "Tested getblockinfo."
|
||||
|
||||
# gettransaction
|
||||
# (GET) http://cyphernode:8888/gettransaction/af867c86000da76df7ddb1054b273ca9e034e8c89d049b5b2795f9f590f67648
|
||||
|
||||
echo "Testing gettransaction..."
|
||||
response=$(curl -s cyphernode:8888/gettransaction/af867c86000da76df7ddb1054b273ca9e034e8c89d049b5b2795f9f590f67648)
|
||||
echo "response=${response}"
|
||||
local txid=$(echo ${response} | jq ".result.txid" | tr -d '\"')
|
||||
echo "txid=${txid}"
|
||||
if [ "${txid}" != "af867c86000da76df7ddb1054b273ca9e034e8c89d049b5b2795f9f590f67648" ]; then
|
||||
exit 8
|
||||
fi
|
||||
echo "Tested gettransaction."
|
||||
|
||||
# getnewaddress
|
||||
# (GET) http://cyphernode:8888/getnewaddress
|
||||
# returns {"address":"2MuiUu8AyuByAGYRDAqqhdYxt8gXcsQ1Ymw"}
|
||||
|
||||
echo "Testing getnewaddress..."
|
||||
response=$(curl -s cyphernode:8888/getnewaddress)
|
||||
echo "response=${response}"
|
||||
address1=$(echo ${response} | jq ".address" | tr -d '\"')
|
||||
echo "address1=${address1}"
|
||||
if [ -z "${address1}" ]; then
|
||||
exit 10
|
||||
fi
|
||||
address2=$(curl -s cyphernode:8888/getnewaddress | jq ".address" | tr -d '\"')
|
||||
echo "address2=${address2}"
|
||||
echo "Tested getnewaddress."
|
||||
|
||||
# getbalance
|
||||
# (GET) http://cyphernode:8888/getbalance
|
||||
|
||||
echo "Testing getbalance..."
|
||||
response=$(curl -s cyphernode:8888/getbalance)
|
||||
echo "response=${response}"
|
||||
local balance=$(echo ${response} | jq ".balance")
|
||||
echo "balance=${balance}"
|
||||
if [ -z "${balance}" ]; then
|
||||
exit 12
|
||||
fi
|
||||
echo "Tested getbalance."
|
||||
|
||||
# watch
|
||||
# POST http://cyphernode:8888/watch
|
||||
# BODY {"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","unconfirmedCallbackURL":"192.168.122.233:1111/callback0conf","confirmedCallbackURL":"192.168.122.233:1111/callback1conf"}
|
||||
|
||||
echo "Testing watch..."
|
||||
local url1="$(hostname):1111/callback0conf"
|
||||
local url2="$(hostname):1111/callback1conf"
|
||||
echo "url1=${url1}"
|
||||
echo "url2=${url2}"
|
||||
response=$(curl -s -H "Content-Type: application/json" -d "{\"address\":\"${address1}\",\"unconfirmedCallbackURL\":\"${url1}\",\"confirmedCallbackURL\":\"${url2}\"}" cyphernode:8888/watch)
|
||||
echo "response=${response}"
|
||||
|
||||
local id=$(echo "${response}" | jq ".id" | tr -d '\"')
|
||||
echo "id=${id}"
|
||||
local event=$(echo "${response}" | jq ".event" | tr -d '\"')
|
||||
echo "event=${event}"
|
||||
if [ "${event}" != "watch" ]; then
|
||||
exit 15
|
||||
fi
|
||||
address=$(echo "${response}" | jq ".address" | tr -d '\"')
|
||||
echo "address=${address}"
|
||||
if [ "${address}" != "${address1}" ]; then
|
||||
exit 20
|
||||
fi
|
||||
local imported=$(echo "${response}" | jq ".imported" | tr -d '\"')
|
||||
echo "imported=${imported}"
|
||||
if [ "${imported}" != "1" ]; then
|
||||
exit 30
|
||||
fi
|
||||
local inserted=$(echo "${response}" | jq ".inserted" | tr -d '\"')
|
||||
echo "inserted=${inserted}"
|
||||
if [ "${inserted}" != "1" ]; then
|
||||
exit 40
|
||||
fi
|
||||
local unconfirmedCallbackURL=$(echo "${response}" | jq ".unconfirmedCallbackURL" | tr -d '\"')
|
||||
echo "unconfirmedCallbackURL=${unconfirmedCallbackURL}"
|
||||
if [ "${unconfirmedCallbackURL}" != "${url1}" ]; then
|
||||
exit 60
|
||||
fi
|
||||
local confirmedCallbackURL=$(echo "${response}" | jq ".confirmedCallbackURL" | tr -d '\"')
|
||||
echo "confirmedCallbackURL=${confirmedCallbackURL}"
|
||||
if [ "${confirmedCallbackURL}" != "${url2}" ]; then
|
||||
exit 70
|
||||
fi
|
||||
|
||||
# Let's watch another address just to be able to test unwatch later and test if found in getactivewatches
|
||||
response=$(curl -s -H "Content-Type: application/json" -d "{\"address\":\"${address2}\",\"unconfirmedCallbackURL\":\"${url1}2\",\"confirmedCallbackURL\":\"${url2}2\"}" cyphernode:8888/watch)
|
||||
echo "response=${response}"
|
||||
echo "Tested watch."
|
||||
|
||||
# getactivewatches
|
||||
# (GET) http://cyphernode:8888/getactivewatches
|
||||
|
||||
echo "Testing getactivewatches..."
|
||||
response=$(curl -s cyphernode:8888/getactivewatches)
|
||||
echo "response=${response}"
|
||||
response=$(echo ${response} | jq ".watches[]")
|
||||
echo "response=${response}"
|
||||
local id2=$(echo ${response} | jq "select(.address == \"${address1}\") | .id" | tr -d '\"')
|
||||
echo "id2=${id2}"
|
||||
if [ "${id2}" != "${id}" ]; then
|
||||
exit 80
|
||||
fi
|
||||
id2=$(echo ${response} | jq "select(.address == \"${address2}\") | .id" | tr -d '\"')
|
||||
echo "id2=${id2}"
|
||||
if [ -z "${id2}" ]; then
|
||||
exit 90
|
||||
fi
|
||||
echo "Tested getactivewatches."
|
||||
|
||||
# unwatch
|
||||
# (GET) http://cyphernode:8888/unwatch/2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp
|
||||
|
||||
echo "Testing unwatch..."
|
||||
response=$(curl -s cyphernode:8888/unwatch/${address2})
|
||||
echo "response=${response}"
|
||||
event=$(echo "${response}" | jq ".event" | tr -d '\"')
|
||||
echo "event=${event}"
|
||||
if [ "${event}" != "unwatch" ]; then
|
||||
exit 100
|
||||
fi
|
||||
address=$(echo "${response}" | jq ".address" | tr -d '\"')
|
||||
echo "address=${address}"
|
||||
if [ "${address}" != "${address2}" ]; then
|
||||
exit 110
|
||||
fi
|
||||
response=$(curl -s cyphernode:8888/getactivewatches)
|
||||
echo "response=${response}"
|
||||
response=$(echo "${response}" | jq ".watches[]")
|
||||
echo "response=${response}"
|
||||
id2=$(echo ${response} | jq "select(.address == \"${address2}\") | .id" | tr -d '\"')
|
||||
echo "id2=${id2}"
|
||||
if [ -n "${id2}" ]; then
|
||||
exit 120
|
||||
fi
|
||||
echo "Tested unwatch."
|
||||
|
||||
# deriveindex
|
||||
# (GET) http://cyphernode:8888/deriveindex/25-30
|
||||
# {"addresses":[{"address":"2N6Q9kBcLtNswgMSLSQ5oduhbctk7hxEJW8"},{"address":"2NFLhFghAPKEPuZCKoeXYYxuaBxhKXbmhBV"},{"address":"2N7gepbQtRM5Hm4PTjvGadj9wAwEwnAsKiP"},{"address":"2Mth8XDZpXkY9d95tort8HYEAuEesow2tF6"},{"address":"2MwqEmAXhUw6H7bJwMhD13HGWVEj2HgFiNH"},{"address":"2N2Y4BVRdrRFhweub2ehHXveGZC3nryMEJw"}]}
|
||||
|
||||
echo "Testing deriveindex..."
|
||||
response=$(curl -v cyphernode:8888/deriveindex/25-30)
|
||||
echo "response=${response}"
|
||||
local nbaddr=$(echo "${response}" | jq ".addresses | length")
|
||||
if [ "${nbaddr}" -ne "6" ]; then
|
||||
exit 130
|
||||
fi
|
||||
address=$(echo "${response}" | jq ".addresses[2].address" | tr -d '\"')
|
||||
if [ "${address}" != "2N7gepbQtRM5Hm4PTjvGadj9wAwEwnAsKiP" ]; then
|
||||
exit 140
|
||||
fi
|
||||
echo "Tested deriveindex."
|
||||
|
||||
# derivepubpath
|
||||
# (GET) http://cyphernode:8888/derivepubpath
|
||||
# BODY {"pub32":"upub5GtUcgGed1aGH4HKQ3vMYrsmLXwmHhS1AeX33ZvDgZiyvkGhNTvGd2TA5Lr4v239Fzjj4ZY48t6wTtXUy2yRgapf37QHgt6KWEZ6bgsCLpb","path":"0/25-30"}
|
||||
# {"addresses":[{"address":"2N6Q9kBcLtNswgMSLSQ5oduhbctk7hxEJW8"},{"address":"2NFLhFghAPKEPuZCKoeXYYxuaBxhKXbmhBV"},{"address":"2N7gepbQtRM5Hm4PTjvGadj9wAwEwnAsKiP"},{"address":"2Mth8XDZpXkY9d95tort8HYEAuEesow2tF6"},{"address":"2MwqEmAXhUw6H7bJwMhD13HGWVEj2HgFiNH"},{"address":"2N2Y4BVRdrRFhweub2ehHXveGZC3nryMEJw"}]}
|
||||
|
||||
echo "Testing derivepubpath..."
|
||||
response=$(curl -v -H "Content-Type: application/json" -d "{\"pub32\":\"upub5GtUcgGed1aGH4HKQ3vMYrsmLXwmHhS1AeX33ZvDgZiyvkGhNTvGd2TA5Lr4v239Fzjj4ZY48t6wTtXUy2yRgapf37QHgt6KWEZ6bgsCLpb\",\"path\":\"0/25-30\"}" cyphernode:8888/derivepubpath)
|
||||
echo "response=${response}"
|
||||
local nbaddr=$(echo "${response}" | jq ".addresses | length")
|
||||
if [ "${nbaddr}" -ne "6" ]; then
|
||||
exit 150
|
||||
fi
|
||||
address=$(echo "${response}" | jq ".addresses[2].address" | tr -d '\"')
|
||||
if [ "${address}" != "2N7gepbQtRM5Hm4PTjvGadj9wAwEwnAsKiP" ]; then
|
||||
exit 160
|
||||
fi
|
||||
echo "Tested derivepubpath."
|
||||
|
||||
# spend
|
||||
# POST http://cyphernode:8888/spend
|
||||
# BODY {"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","amount":0.00233}
|
||||
|
||||
# By spending to a watched address, we will test the spending feature and trigger the confirmation to test
|
||||
# confirmations of watched addresses... Cleva!!!
|
||||
|
||||
echo "Testing spend, conf and callbacks..."
|
||||
response=$(curl -v -H "Content-Type: application/json" -d "{\"address\":\"${address1}\",\"amount\":0.00001}" cyphernode:8888/spend)
|
||||
echo "response=${response}"
|
||||
wait_for_callbacks
|
||||
echo "Tested spend, conf and callbacks."
|
||||
|
||||
# addtobatch
|
||||
# POST http://cyphernode:8888/addtobatch
|
||||
# BODY {"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","amount":0.00233}
|
||||
|
||||
# By spending to a watched address, we will test the spending feature and trigger the confirmation to test
|
||||
# confirmations of watched addresses... Cleva!!!
|
||||
|
||||
# echo "Testing addtobatch..."
|
||||
# response=$(curl -v -H "Content-Type: application/json" -d "{\"address\":\"${address1}\",\"amount\":0.00001}" cyphernode:8888/spend)
|
||||
# echo "response=${response}"
|
||||
# wait_for_callbacks
|
||||
# echo "Tested addtobatch ."
|
||||
|
||||
|
||||
|
||||
|
||||
# conf
|
||||
# (GET) http://cyphernode:8888/conf/b081ca7724386f549cf0c16f71db6affeb52ff7a0d9b606fb2e5c43faffd3387
|
||||
|
||||
# Let's trigger tx confirmation even if not confirmed. Will be funny. Should take care of
|
||||
# multiple confirmations of the same state.
|
||||
|
||||
|
||||
|
||||
# executecallbacks
|
||||
# (GET) http://cyphernode::8080/executecallbacks
|
||||
|
||||
#echo "GET /getbestblockinfo" | nc cyphernode:8888 - | sed -En "s/^(\{.*)/\1/p" | jq
|
||||
|
||||
|
||||
|
||||
|
||||
# spend
|
||||
# POST http://cyphernode:8888/spend
|
||||
# BODY {"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","amount":0.00233}
|
||||
|
||||
#curl -v -H "Content-Type: application/json" -d '{"address":"2MsWyaQ8APbnqasFpWopqUKqsdpiVY3EwLE","amount":0.0001}' cyphernode:8888/spend
|
||||
|
||||
# ln_getinfo
|
||||
# (GET) http://cyphernode:8888/ln_getinfo
|
||||
|
||||
echo "Testing ln_getinfo..."
|
||||
response=$(curl -s cyphernode:8888/ln_getinfo)
|
||||
echo "response=${response}"
|
||||
local port=$(echo ${response} | jq ".binding[] | select(.type == \"ipv4\") | .port")
|
||||
echo "port=${port}"
|
||||
if [ "${port}" != "9735" ]; then
|
||||
exit 170
|
||||
fi
|
||||
echo "Tested ln_getinfo."
|
||||
|
||||
# ln_newaddr
|
||||
# (GET) http://cyphernode:8888/ln_newaddr
|
||||
|
||||
echo "Testing ln_newaddr..."
|
||||
response=$(curl -s cyphernode:8888/ln_newaddr)
|
||||
echo "response=${response}"
|
||||
address=$(echo ${response} | jq ".address")
|
||||
echo "address=${address}"
|
||||
if [ -z "${address}" ]; then
|
||||
exit 180
|
||||
fi
|
||||
echo "Tested ln_newaddr."
|
||||
|
||||
# ln_create_invoice
|
||||
# POST http://cyphernode:8888/ln_create_invoice
|
||||
# BODY {"msatoshi":"10000","label":"koNCcrSvhX3dmyFhW","description":"Bylls order #10649","expiry":"10"}
|
||||
|
||||
#echo "Testing ln_create_invoice..."
|
||||
#response=$(curl -v -H "Content-Type: application/json" -d "{\"msatoshi\":10000,\"label\":\"koNCcrSvhX3dmyFhW\",\"description\":\"Bylls order #10649\",\"expiry\":10}" cyphernode:8888/ln_create_invoice)
|
||||
#echo "response=${response}"
|
||||
|
||||
#echo "Tested ln_create_invoice."
|
||||
|
||||
# ln_pay
|
||||
|
||||
|
||||
}
|
||||
|
||||
wait_for_callbacks()
|
||||
{
|
||||
nc -vlp1111 -e ./tests-cb.sh
|
||||
nc -vlp1111 -e ./tests-cb.sh
|
||||
}
|
||||
|
||||
tests
|
||||
15
proxy_docker/app/script/trace.sh
Normal file
15
proxy_docker/app/script/trace.sh
Normal file
@@ -0,0 +1,15 @@
|
||||
#!/bin/sh
|
||||
|
||||
trace()
|
||||
{
|
||||
if [ -n "${TRACING}" ]; then
|
||||
echo "$(date -Is) ${1}" > /dev/stderr
|
||||
fi
|
||||
}
|
||||
|
||||
trace_rc()
|
||||
{
|
||||
if [ -n "${TRACING}" ]; then
|
||||
echo "$(date -Is) Last return code: ${1}" > /dev/stderr
|
||||
fi
|
||||
}
|
||||
27
proxy_docker/app/script/unwatchrequest.sh
Normal file
27
proxy_docker/app/script/unwatchrequest.sh
Normal file
@@ -0,0 +1,27 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./trace.sh
|
||||
. ./sql.sh
|
||||
|
||||
unwatchrequest()
|
||||
{
|
||||
trace "Entering unwatchrequest()..."
|
||||
|
||||
local request=${1}
|
||||
local address=$(echo "${request}" | cut -d ' ' -f2 | cut -d '/' -f3)
|
||||
local returncode
|
||||
trace "[unwatchrequest] Unwatch request on address ${address})"
|
||||
|
||||
sql "UPDATE watching SET watching=0 WHERE address=\"${address}\""
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
|
||||
data="{\"event\":\"unwatch\",\"address\":\"${address}\"}"
|
||||
trace "[unwatchrequest] responding=${data}"
|
||||
|
||||
echo "${data}"
|
||||
|
||||
return ${returncode}
|
||||
}
|
||||
|
||||
case "${0}" in *unwatchrequest.sh) unwatchrequest $@;; esac
|
||||
17
proxy_docker/app/script/utils.sh
Normal file
17
proxy_docker/app/script/utils.sh
Normal file
@@ -0,0 +1,17 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./trace.sh
|
||||
|
||||
get_prop()
|
||||
{
|
||||
trace "Entering get_prop()..."
|
||||
|
||||
local property=${1}
|
||||
trace "[get_prop] property=${property}"
|
||||
|
||||
local value=$(grep "${property}" config.properties | cut -d'=' -f2)
|
||||
|
||||
trace "[get_prop] value=${value}"
|
||||
|
||||
echo ${value}
|
||||
}
|
||||
193
proxy_docker/app/script/walletoperations.sh
Normal file
193
proxy_docker/app/script/walletoperations.sh
Normal file
@@ -0,0 +1,193 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./trace.sh
|
||||
. ./sendtobitcoinnode.sh
|
||||
|
||||
spend()
|
||||
{
|
||||
trace "Entering spend()..."
|
||||
|
||||
local data
|
||||
local request=${1}
|
||||
local address=$(echo "${request}" | jq ".address" | tr -d '"')
|
||||
trace "[spend] address=${address}"
|
||||
local amount=$(echo "${request}" | jq ".amount" | awk '{ printf "%.8f", $0 }')
|
||||
trace "[spend] amount=${amount}"
|
||||
local response
|
||||
local id_inserted
|
||||
|
||||
response=$(send_to_spender_node "{\"method\":\"sendtoaddress\",\"params\":[\"${address}\",${amount}]}")
|
||||
local returncode=$?
|
||||
trace_rc ${returncode}
|
||||
trace "[spend] response=${response}"
|
||||
|
||||
if [ "${returncode}" -eq 0 ]; then
|
||||
local txid=$(echo ${response} | jq ".result" | tr -d '"')
|
||||
trace "[spend] txid=${txid}"
|
||||
|
||||
# Let's insert the txid in our little DB to manage the confirmation and tell it's not a watching address
|
||||
# sql "INSERT OR IGNORE INTO watching (watching, txid) VALUES (0, ${txid})"
|
||||
sql "INSERT OR IGNORE INTO tx (txid) VALUES (\"${txid}\")"
|
||||
trace_rc $?
|
||||
id_inserted=$(sql "SELECT id FROM tx WHERE txid=\"${txid}\"")
|
||||
trace_rc $?
|
||||
sql "INSERT OR IGNORE INTO recipient (address, amount, tx_id) VALUES (\"${address}\", ${amount}, ${id_inserted})"
|
||||
trace_rc $?
|
||||
|
||||
data="{\"status\":\"accepted\""
|
||||
data="${data},\"hash\":\"${txid}\"}"
|
||||
else
|
||||
local message=$(echo "${response}" | jq -e ".error.message")
|
||||
data="{\"message\":${message}}"
|
||||
fi
|
||||
|
||||
trace "[spend] responding=${data}"
|
||||
echo "${data}"
|
||||
|
||||
return ${returncode}
|
||||
}
|
||||
|
||||
getbalance()
|
||||
{
|
||||
trace "Entering getbalance()..."
|
||||
|
||||
local response
|
||||
local data='{"method":"getbalance"}'
|
||||
response=$(send_to_spender_node "${data}")
|
||||
local returncode=$?
|
||||
trace_rc ${returncode}
|
||||
trace "[getbalance] response=${response}"
|
||||
|
||||
if [ "${returncode}" -eq 0 ]; then
|
||||
local balance=$(echo ${response} | jq ".result")
|
||||
trace "[getbalance] balance=${balance}"
|
||||
|
||||
data="{\"balance\":${balance}}"
|
||||
else
|
||||
trace "[getbalance] Coudn't get balance!"
|
||||
data=""
|
||||
fi
|
||||
|
||||
trace "[getbalance] responding=${data}"
|
||||
echo "${data}"
|
||||
|
||||
return ${returncode}
|
||||
}
|
||||
|
||||
getnewaddress()
|
||||
{
|
||||
trace "Entering getnewaddress()..."
|
||||
|
||||
local response
|
||||
local data='{"method":"getnewaddress"}'
|
||||
response=$(send_to_spender_node "${data}")
|
||||
local returncode=$?
|
||||
trace_rc ${returncode}
|
||||
trace "[getnewaddress] response=${response}"
|
||||
|
||||
if [ "${returncode}" -eq 0 ]; then
|
||||
local address=$(echo ${response} | jq ".result")
|
||||
trace "[getnewaddress] address=${address}"
|
||||
|
||||
data="{\"address\":${address}}"
|
||||
else
|
||||
trace "[getnewaddress] Coudn't get a new address!"
|
||||
data=""
|
||||
fi
|
||||
|
||||
trace "[getnewaddress] responding=${data}"
|
||||
echo "${data}"
|
||||
|
||||
return ${returncode}
|
||||
}
|
||||
|
||||
addtobatching()
|
||||
{
|
||||
trace "Entering addtobatching()..."
|
||||
|
||||
local address=${1}
|
||||
trace "[addtobatching] address=${address}"
|
||||
local amount=${2}
|
||||
trace "[addtobatching] amount=${amount}"
|
||||
|
||||
sql "INSERT OR IGNORE INTO recipient (address, amount) VALUES (\"${address}\", ${amount})"
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
|
||||
return ${returncode}
|
||||
}
|
||||
|
||||
batchspend()
|
||||
{
|
||||
trace "Entering batchspend()..."
|
||||
|
||||
local data
|
||||
local response
|
||||
local recipientswhere
|
||||
local recipientsjson
|
||||
local id_inserted
|
||||
|
||||
# We will batch all the addresses in DB without a TXID
|
||||
local batching=$(sql 'SELECT address, amount FROM recipient WHERE tx_id IS NULL')
|
||||
trace "[batchspend] batching=${batching}"
|
||||
|
||||
local returncode
|
||||
local address
|
||||
local amount
|
||||
local notfirst=false
|
||||
local IFS=$'\n'
|
||||
for row in ${batching}
|
||||
do
|
||||
trace "[batchspend] row=${row}"
|
||||
address=$(echo "${row}" | cut -d '|' -f1)
|
||||
trace "[batchspend] address=${address}"
|
||||
amount=$(echo "${row}" | cut -d '|' -f2)
|
||||
trace "[batchspend] amount=${amount}"
|
||||
|
||||
if ${notfirst}; then
|
||||
recipientswhere="${recipientswhere},"
|
||||
recipientsjson="${recipientsjson},"
|
||||
else
|
||||
notfirst=true
|
||||
fi
|
||||
|
||||
recipientswhere="${recipientswhere}\"${address}\""
|
||||
recipientsjson="${recipientsjson}\"${address}\":${amount}"
|
||||
done
|
||||
|
||||
response=$(send_to_spender_node "{\"method\":\"sendmany\",\"params\":[\"\", {${recipientsjson}}]}")
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
trace "[batchspend] response=${response}"
|
||||
|
||||
if [ "${returncode}" -eq 0 ]; then
|
||||
local txid=$(echo ${response} | jq ".result" | tr -d '"')
|
||||
trace "[batchspend] txid=${txid}"
|
||||
|
||||
# Let's insert the txid in our little DB to manage the confirmation and tell it's not a watching address
|
||||
# sql "INSERT OR IGNORE INTO watching (watching, txid) VALUES (0, ${txid})"
|
||||
# trace_rc $?
|
||||
sql "INSERT OR IGNORE INTO tx (txid) VALUES (\"${txid}\")"
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
if [ "${returncode}" -eq 0 ]; then
|
||||
id_inserted=$(sql "SELECT id FROM tx WHERE txid=\"${txid}\"")
|
||||
trace "[batchspend] id_inserted: ${id_inserted}"
|
||||
sql "UPDATE recipient SET tx_id=${id_inserted} WHERE address IN (${recipientswhere})"
|
||||
trace_rc $?
|
||||
fi
|
||||
|
||||
data="{\"status\":\"accepted\""
|
||||
data="${data},\"hash\":\"${txid}\"}"
|
||||
else
|
||||
local message=$(echo "${response}" | jq -e ".error.message")
|
||||
data="{\"message\":${message}}"
|
||||
fi
|
||||
|
||||
trace "[batchspend] responding=${data}"
|
||||
echo "${data}"
|
||||
|
||||
return ${returncode}
|
||||
}
|
||||
|
||||
#case "${0}" in *walletoperations.sh) getbalance $@;; esac
|
||||
75
proxy_docker/app/script/watchrequest.sh
Normal file
75
proxy_docker/app/script/watchrequest.sh
Normal file
@@ -0,0 +1,75 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./trace.sh
|
||||
. ./importaddress.sh
|
||||
. ./sql.sh
|
||||
. ./sendtobitcoinnode.sh
|
||||
|
||||
watchrequest()
|
||||
{
|
||||
trace "Entering watchrequest()..."
|
||||
|
||||
local returncode
|
||||
local request=${1}
|
||||
local address=$(echo "${request}" | jq ".address" | tr -d '"')
|
||||
local cb0conf_url=$(echo "${request}" | jq ".unconfirmedCallbackURL" | tr -d '"')
|
||||
local cb1conf_url=$(echo "${request}" | jq ".confirmedCallbackURL" | tr -d '"')
|
||||
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})"
|
||||
|
||||
result=$(importaddress_rpc "${address}")
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
if [ "${returncode}" -eq 0 ]; then
|
||||
imported=1
|
||||
else
|
||||
imported=0
|
||||
fi
|
||||
|
||||
# sql "INSERT OR IGNORE INTO watching (address, watching, callback0conf, callback1conf, imported) VALUES (\"${address}\", 1, \"${cb0conf_url}\", \"${cb1conf_url}\", ${imported})"
|
||||
sql "INSERT OR IGNORE INTO watching (address, watching, callback0conf, callback1conf, imported) VALUES (\"${address}\", 1, \"${cb0conf_url}\", \"${cb1conf_url}\", ${imported})"
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
if [ "${returncode}" -eq 0 ]; then
|
||||
inserted=1
|
||||
id_inserted=$(sql "SELECT id FROM watching WHERE address='${address}'")
|
||||
trace "[watchrequest] id_inserted: ${id_inserted}"
|
||||
else
|
||||
inserted=0
|
||||
fi
|
||||
|
||||
local fees2blocks
|
||||
local fees6blocks
|
||||
local fees36blocks
|
||||
local fees144blocks
|
||||
fees2blocks=$(getestimatesmartfee 2)
|
||||
trace_rc $?
|
||||
fees6blocks=$(getestimatesmartfee 6)
|
||||
trace_rc $?
|
||||
fees36blocks=$(getestimatesmartfee 36)
|
||||
trace_rc $?
|
||||
fees144blocks=$(getestimatesmartfee 144)
|
||||
trace_rc $?
|
||||
|
||||
local data="{\"id\":\"${id_inserted}\",
|
||||
\"event\":\"watch\",
|
||||
\"imported\":\"${imported}\",
|
||||
\"inserted\":\"${inserted}\",
|
||||
\"address\":\"${address}\",
|
||||
\"unconfirmedCallbackURL\":\"${cb0conf_url}\",
|
||||
\"confirmedCallbackURL\":\"${cb1conf_url}\",
|
||||
\"estimatesmartfee2blocks\":\"${fees2blocks}\",
|
||||
\"estimatesmartfee6blocks\":\"${fees6blocks}\",
|
||||
\"estimatesmartfee36blocks\":\"${fees36blocks}\",
|
||||
\"estimatesmartfee144blocks\":\"${fees144blocks}\"}"
|
||||
trace "[watchrequest] responding=${data}"
|
||||
|
||||
echo "${data}"
|
||||
|
||||
return ${returncode}
|
||||
}
|
||||
|
||||
case "${0}" in *watchrequest.sh) watchrequest $@;; esac
|
||||
11
proxy_docker/env.properties
Normal file
11
proxy_docker/env.properties
Normal file
@@ -0,0 +1,11 @@
|
||||
TRACING=1
|
||||
WATCHER_BTC_NODE_RPC_URL=btcnode:18332/wallet/watching01.dat
|
||||
SPENDER_BTC_NODE_RPC_URL=btcnode:18332/wallet/spending01.dat
|
||||
PROXY_LISTENING_PORT=8888
|
||||
# Variable substitutions don't work
|
||||
DB_PATH=/proxyuser/db
|
||||
DB_FILE=/proxyuser/db/proxydb
|
||||
# Pycoin container
|
||||
PYCOIN_CONTAINER=pycoinnode:7777
|
||||
# OTS container
|
||||
OTS_CONTAINER=otsnode:6666
|
||||
31
pycoin_docker/Dockerfile
Normal file
31
pycoin_docker/Dockerfile
Normal file
@@ -0,0 +1,31 @@
|
||||
#FROM resin/raspberry-pi-alpine-python:3.6
|
||||
FROM python:3.6-alpine
|
||||
|
||||
ARG USER_ID
|
||||
ARG GROUP_ID
|
||||
ENV USERNAME proxyuser
|
||||
ENV HOME /${USERNAME}
|
||||
ENV USER_ID ${USER_ID:-1000}
|
||||
ENV GROUP_ID ${GROUP_ID:-1000}
|
||||
|
||||
RUN addgroup -g ${GROUP_ID} ${USERNAME} \
|
||||
&& adduser -u ${USER_ID} -G ${USERNAME} -D -s /bin/sh -h ${HOME} ${USERNAME}
|
||||
|
||||
RUN apk add --update --no-cache git jq \
|
||||
&& pip install --no-cache-dir pycoin \
|
||||
&& cd \
|
||||
&& git clone https://github.com/Kexkey/pycoin.git \
|
||||
&& cp -rf pycoin/pycoin/* /usr/local/lib/python3.6/site-packages/pycoin
|
||||
|
||||
COPY --chown=proxyuser script/pycoin.sh ${HOME}/pycoin.sh
|
||||
COPY --chown=proxyuser script/requesthandler.sh ${HOME}/requesthandler.sh
|
||||
COPY --chown=proxyuser script/responsetoclient.sh ${HOME}/responsetoclient.sh
|
||||
COPY --chown=proxyuser script/startpycoin.sh ${HOME}/startpycoin.sh
|
||||
COPY --chown=proxyuser script/trace.sh ${HOME}/trace.sh
|
||||
|
||||
USER ${USERNAME}
|
||||
WORKDIR ${HOME}
|
||||
|
||||
RUN chmod +x startpycoin.sh requesthandler.sh
|
||||
|
||||
ENTRYPOINT ["./startpycoin.sh"]
|
||||
16
pycoin_docker/README.md
Normal file
16
pycoin_docker/README.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# Build image
|
||||
|
||||
```shell
|
||||
docker build -t pycoinimg --build-arg USER_ID=$(id -u proxyuser) --build-arg GROUP_ID=$(id -g proxyuser) .
|
||||
```
|
||||
|
||||
# Usefull examples
|
||||
|
||||
See https://github.com/shivaenigma/pycoin
|
||||
|
||||
List SegWit addresses for path 0/24-30 for a pub32:
|
||||
|
||||
```shell
|
||||
curl -H "Content-Type: application/json" -d '{"pub32":"tpubD6NzVbkrYhZ4YR3QK2tyfMMvBghAvqtNaNK1LTyDWcRHLcMUm3ZN2cGm5BS3MhCRCeCkXQkTXXjiJgqxpqXK7PeUSp86DTTgkLpcjMtpKWk","path":"0/25-30"}' http://localhost:7777/derive
|
||||
curl -H "Content-Type: application/json" -d '{"pub32":"vpub5SLqN2bLY4WeZF3kL4VqiWF1itbf3A6oRrq9aPf16AZMVWYCuN9TxpAZwCzVgW94TNzZPNc9XAHD4As6pdnExBtCDGYRmNJrcJ4eV9hNqcv","path":"0/25-30"}' http://localhost:7777/derive
|
||||
```
|
||||
2
pycoin_docker/env.properties
Normal file
2
pycoin_docker/env.properties
Normal file
@@ -0,0 +1,2 @@
|
||||
TRACING=1
|
||||
PYCOIN_LISTENING_PORT=7777
|
||||
47
pycoin_docker/script/pycoin.sh
Normal file
47
pycoin_docker/script/pycoin.sh
Normal file
@@ -0,0 +1,47 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./trace.sh
|
||||
|
||||
derive()
|
||||
{
|
||||
trace "Entering derive()..."
|
||||
|
||||
local request=${1}
|
||||
local pub32=$(echo "${request}" | jq ".pub32" | tr -d '"')
|
||||
local path=$(echo "${request}" | jq ".path" | tr -d '"')
|
||||
|
||||
local result
|
||||
local returncode
|
||||
trace "[derive] path=${path}"
|
||||
trace "[derive] pub32=${pub32}"
|
||||
|
||||
result=$(ku -n BTC -s ${path} -a E:${pub32})
|
||||
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
trace "[derive] result=${result}"
|
||||
|
||||
local notfirst=false
|
||||
|
||||
echo -n "{\"addresses\":["
|
||||
|
||||
local IFS=$'\n'
|
||||
for address in ${result}
|
||||
do
|
||||
if ${notfirst}; then
|
||||
echo ","
|
||||
else
|
||||
notfirst=true
|
||||
fi
|
||||
trace "[derive] address=${address}"
|
||||
|
||||
data="{\"address\":\"${address}\"}"
|
||||
trace "[derive] data=${data}"
|
||||
|
||||
echo -n "${data}"
|
||||
done
|
||||
|
||||
echo "]}"
|
||||
|
||||
return ${returncode}
|
||||
}
|
||||
84
pycoin_docker/script/requesthandler.sh
Normal file
84
pycoin_docker/script/requesthandler.sh
Normal file
@@ -0,0 +1,84 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
. ./pycoin.sh
|
||||
. ./responsetoclient.sh
|
||||
. ./trace.sh
|
||||
|
||||
main()
|
||||
{
|
||||
trace "Entering main()..."
|
||||
|
||||
local step=0
|
||||
local cmd
|
||||
local http_method
|
||||
local line
|
||||
local content_length
|
||||
local response
|
||||
local returncode
|
||||
|
||||
while read line; do
|
||||
line=$(echo "${line}" | tr -d '\r\n')
|
||||
trace "[main] line=${line}"
|
||||
|
||||
if [ "${cmd}" = "" ]; then
|
||||
# First line!
|
||||
# Looking for something like:
|
||||
# GET /cmd/params HTTP/1.1
|
||||
# POST / HTTP/1.1
|
||||
cmd=$(echo "${line}" | cut -d '/' -f2 | cut -d ' ' -f1)
|
||||
trace "[main] cmd=${cmd}"
|
||||
http_method=$(echo "${line}" | cut -d ' ' -f1)
|
||||
trace "[main] http_method=${http_method}"
|
||||
if [ "${http_method}" = "GET" ]; then
|
||||
step=1
|
||||
fi
|
||||
fi
|
||||
if [ "${line}" = "" ]; then
|
||||
trace "[main] empty line"
|
||||
if [ ${step} -eq 1 ]; then
|
||||
trace "[main] body part finished, disconnecting"
|
||||
break
|
||||
else
|
||||
trace "[main] headers part finished, body incoming"
|
||||
step=1
|
||||
fi
|
||||
fi
|
||||
# line=content-length: 406
|
||||
case "${line}" in *[cC][oO][nN][tT][eE][nN][tT]-[lL][eE][nN][gG][tT][hH]*)
|
||||
content_length=$(echo ${line} | cut -d ':' -f2)
|
||||
trace "[main] content_length=${content_length}";
|
||||
;;
|
||||
esac
|
||||
if [ ${step} -eq 1 ]; then
|
||||
trace "[main] step=${step}"
|
||||
if [ "${http_method}" = "POST" ]; then
|
||||
read -n ${content_length} line
|
||||
trace "[main] line=${line}"
|
||||
fi
|
||||
case "${cmd}" in
|
||||
derive)
|
||||
# POST http://192.168.111.152:7777/derive
|
||||
# BODY {"pub32":"tpubD6NzVbkrYhZ4YR3QK2tyfMMvBghAvqtNaNK1LTyDWcRHLcMUm3ZN2cGm5BS3MhCRCeCkXQkTXXjiJgqxpqXK7PeUSp86DTTgkLpcjMtpKWk","path":"0/25-30"}
|
||||
# BODY {"pub32":"upub5GtUcgGed1aGH4HKQ3vMYrsmLXwmHhS1AeX33ZvDgZiyvkGhNTvGd2TA5Lr4v239Fzjj4ZY48t6wTtXUy2yRgapf37QHgt6KWEZ6bgsCLpb","path":"0/25-30"}
|
||||
# BODY {"pub32":"vpub5SLqN2bLY4WeZF3kL4VqiWF1itbf3A6oRrq9aPf16AZMVWYCuN9TxpAZwCzVgW94TNzZPNc9XAHD4As6pdnExBtCDGYRmNJrcJ4eV9hNqcv","path":"0/25-30"}
|
||||
|
||||
response=$(derive "${line}")
|
||||
response_to_client "${response}" ${?}
|
||||
break
|
||||
;;
|
||||
esac
|
||||
break
|
||||
fi
|
||||
done
|
||||
trace "[main] exiting"
|
||||
return 0
|
||||
}
|
||||
|
||||
export TRACING
|
||||
|
||||
main
|
||||
exit $?
|
||||
21
pycoin_docker/script/responsetoclient.sh
Normal file
21
pycoin_docker/script/responsetoclient.sh
Normal file
@@ -0,0 +1,21 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./trace.sh
|
||||
|
||||
response_to_client()
|
||||
{
|
||||
trace "Entering response_to_client()..."
|
||||
|
||||
local response=${1}
|
||||
local returncode=${2}
|
||||
|
||||
([ -z "${returncode}" ] || [ "${returncode}" -eq "0" ]) && echo -ne "HTTP/1.1 200 OK\r\n"
|
||||
[ -n "${returncode}" ] && [ "${returncode}" -ne "0" ] && echo -ne "HTTP/1.1 400 Bad Request\r\n"
|
||||
|
||||
echo -e "Content-Type: application/json\r\nContent-Length: ${#response}\r\n\r\n${response}"
|
||||
|
||||
# Small delay needed for the data to be processed correctly by peer
|
||||
sleep 0.2s
|
||||
}
|
||||
|
||||
case "${0}" in *responsetoclient.sh) response_to_client $@;; esac
|
||||
6
pycoin_docker/script/startpycoin.sh
Normal file
6
pycoin_docker/script/startpycoin.sh
Normal file
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
export TRACING
|
||||
export PYCOIN_LISTENING_PORT
|
||||
|
||||
nc -vlkp${PYCOIN_LISTENING_PORT} -e ./requesthandler.sh
|
||||
15
pycoin_docker/script/trace.sh
Normal file
15
pycoin_docker/script/trace.sh
Normal file
@@ -0,0 +1,15 @@
|
||||
#!/bin/sh
|
||||
|
||||
trace()
|
||||
{
|
||||
if [ -n "${TRACING}" ]; then
|
||||
echo "$(date -Is) ${1}" > /dev/stderr
|
||||
fi
|
||||
}
|
||||
|
||||
trace_rc()
|
||||
{
|
||||
if [ -n "${TRACING}" ]; then
|
||||
echo "$(date -Is) Last return code: ${1}" > /dev/stderr
|
||||
fi
|
||||
}
|
||||
Reference in New Issue
Block a user