mirror of
https://github.com/aljazceru/cyphernode.git
synced 2026-02-01 02:34:39 +01:00
Merge pull request #201 from SatoshiPortal/features/batching
Features/batching
This commit is contained in:
@@ -17,6 +17,7 @@ action_getactivewatchesbyxpub=watcher
|
||||
action_getactivewatchesbylabel=watcher
|
||||
action_getactivexpubwatches=watcher
|
||||
action_watchtxid=watcher
|
||||
action_unwatchtxid=watcher
|
||||
action_getactivewatches=watcher
|
||||
action_get_txns_by_watchlabel=watcher
|
||||
action_get_unused_addresses_by_watchlabel=watcher
|
||||
@@ -31,6 +32,7 @@ action_ln_decodebolt11=watcher
|
||||
action_ln_listpeers=watcher
|
||||
action_ln_getroute=watcher
|
||||
action_ln_listpays=watcher
|
||||
action_bitcoin_estimatesmartfee=watcher
|
||||
|
||||
# Spender can do what the watcher can do, plus:
|
||||
action_getxnslist=spender
|
||||
@@ -55,6 +57,12 @@ action_ln_decodebolt11=spender
|
||||
action_ln_connectfund=spender
|
||||
action_ln_listfunds=spender
|
||||
action_ln_withdraw=spender
|
||||
action_createbatcher=spender
|
||||
action_updatebatcher=spender
|
||||
action_removefrombatch=spender
|
||||
action_listbatchers=spender
|
||||
action_getbatcher=spender
|
||||
action_getbatchdetails=spender
|
||||
|
||||
# Admin can do what the spender can do, plus:
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ action_getactivexpubwatches=watcher
|
||||
action_get_txns_by_watchlabel=watcher
|
||||
action_get_unused_addresses_by_watchlabel=watcher
|
||||
action_watchtxid=watcher
|
||||
action_unwatchtxid=watcher
|
||||
action_getactivewatches=watcher
|
||||
action_getbestblockhash=watcher
|
||||
action_getbestblockinfo=watcher
|
||||
@@ -36,6 +37,7 @@ action_ln_decodebolt11=watcher
|
||||
action_ln_listpeers=watcher
|
||||
action_ln_getroute=watcher
|
||||
action_ln_listpays=watcher
|
||||
action_bitcoin_estimatesmartfee=watcher
|
||||
|
||||
# Spender can do what the watcher can do, plus:
|
||||
action_get_txns_spending=spender
|
||||
@@ -59,6 +61,12 @@ action_ln_decodebolt11=spender
|
||||
action_ln_connectfund=spender
|
||||
action_ln_listfunds=spender
|
||||
action_ln_withdraw=spender
|
||||
action_createbatcher=spender
|
||||
action_updatebatcher=spender
|
||||
action_removefrombatch=spender
|
||||
action_listbatchers=spender
|
||||
action_getbatcher=spender
|
||||
action_getbatchdetails=spender
|
||||
|
||||
# Admin can do what the spender can do, plus:
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ start_apps() {
|
||||
export TOR_DATAPATH
|
||||
export LIGHTNING_DATAPATH
|
||||
export BITCOIN_DATAPATH
|
||||
export LOGS_DATAPATH
|
||||
export APP_SCRIPT_PATH
|
||||
export APP_ID
|
||||
export DOCKER_MODE
|
||||
|
||||
365
doc/API.v0.md
365
doc/API.v0.md
@@ -2,9 +2,9 @@
|
||||
|
||||
## Current API
|
||||
|
||||
### Watch a Bitcoin Address (called by application)
|
||||
### Watch a Bitcoin Address (called by your application)
|
||||
|
||||
Inserts the address and callbacks in the DB and imports the address to the Watching wallet. The callback URLs and event message are optional. If eventMessage is not supplied, tx_confirmation for that watch will not be published. Event message should be in base64 format to avoid dealing with escaping special characters.
|
||||
Inserts the address, webhook URLs and eventMessage in the DB and imports the address to the Watching wallet. The webhook URLs (callbackURLs) and event message are optional. If eventMessage is not supplied, the event will not be published to the tx_confirmation topic on confirmations. Event message should be in base64 format to avoid dealing with escaping special characters. The same address can be watched by different requests with different webhook URLs.
|
||||
|
||||
```http
|
||||
POST http://cyphernode:8888/watch
|
||||
@@ -31,12 +31,18 @@ Proxy response:
|
||||
}
|
||||
```
|
||||
|
||||
### Un-watch a previously watched Bitcoin Address (called by application)
|
||||
### Un-watch a previously watched Bitcoin Address (called by your application)
|
||||
|
||||
Updates the watched address row in DB so that callbacks won't be called on tx confirmations for that address.
|
||||
Updates the watched address row in DB so that webhooks won't be called on tx confirmations for that address. You can POST the URLs to make sure you unwatch the good watcher, since there may be multiple watchers on the same address with different webhook URLs. You can also, more conveniently, supply the watch id to unwatch.
|
||||
|
||||
```http
|
||||
GET http://cyphernode:8888/unwatch/2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp
|
||||
or
|
||||
POST http://192.168.111.152:8080/unwatch
|
||||
with body...
|
||||
{"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","unconfirmedCallbackURL":"192.168.111.233:1111/callback0conf","confirmedCallbackURL":"192.168.111.233:1111/callback1conf"}
|
||||
or
|
||||
{"id":3124}
|
||||
```
|
||||
|
||||
Proxy response:
|
||||
@@ -44,11 +50,13 @@ Proxy response:
|
||||
```json
|
||||
{
|
||||
"event": "unwatch",
|
||||
"address": "2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp"
|
||||
"address": "2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp",
|
||||
"unconfirmedCallbackURL": "192.168.133.233:1111/callback0conf",
|
||||
"confirmedCallbackURL": "192.168.133.233:1111/callback1conf"
|
||||
}
|
||||
```
|
||||
|
||||
### Get a list of Bitcoin addresses being watched (called by application)
|
||||
### Get a list of Bitcoin addresses being watched (called by your application)
|
||||
|
||||
Returns the list of currently watched addresses and callback information.
|
||||
|
||||
@@ -61,17 +69,19 @@ 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",
|
||||
"eventMessage":"eyJib3VuY2VfYWRkcmVzcyI6IjJNdkEzeHIzOHIxNXRRZWhGblBKMVhBdXJDUFR2ZTZOamNGIiwibmJfY29uZiI6MH0K"}
|
||||
{
|
||||
"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",
|
||||
"eventMessage":"eyJib3VuY2VfYWRkcmVzcyI6IjJNdkEzeHIzOHIxNXRRZWhGblBKMVhBdXJDUFR2ZTZOamNGIiwibmJfY29uZiI6MH0K"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Get a list of txns from a watched label
|
||||
|
||||
Returns the list of transactions not spend(txns) from watched label.
|
||||
@@ -126,7 +136,7 @@ Proxy response:
|
||||
}
|
||||
```
|
||||
|
||||
### Watch a Bitcoin xpub/ypub/zpub/tpub/upub/vpub extended public key (called by application)
|
||||
### Watch a Bitcoin xpub/ypub/zpub/tpub/upub/vpub extended public key (called by your application)
|
||||
|
||||
Used to watch the transactions related to an xpub. It will first derive 100 addresses using the provided xpub, derivation path and index information. It will add those addresses to the watching DB table and add those addresses to the Watching-by-xpub wallet. The watching process will take care of calling the provided callbacks when a transaction occurs. When a transaction is seen, Cyphernode will derive and start watching new addresses related to the xpub, keeping a 100 address gap between the last used address in a transaction and the last watched address of that xpub. The label can be used later, instead of the whole xpub, with unwatchxpub* and and getactivewatchesby*.
|
||||
|
||||
@@ -151,7 +161,7 @@ Proxy response:
|
||||
}
|
||||
```
|
||||
|
||||
### Un-watch a previously watched Bitcoin xpub by providing the xpub (called by application)
|
||||
### Un-watch a previously watched Bitcoin xpub by providing the xpub (called by your application)
|
||||
|
||||
Updates the watched address rows in DB so that callbacks won't be called on tx confirmations for the provided xpub and related addresses.
|
||||
|
||||
@@ -168,7 +178,7 @@ Proxy response:
|
||||
}
|
||||
```
|
||||
|
||||
### Un-watch a previously watched Bitcoin xpub by providing the label (called by application)
|
||||
### Un-watch a previously watched Bitcoin xpub by providing the label (called by your application)
|
||||
|
||||
Updates the watched address rows in DB so that callbacks won't be called on tx confirmations for the provided xpub and related addresses.
|
||||
|
||||
@@ -185,7 +195,7 @@ Proxy response:
|
||||
}
|
||||
```
|
||||
|
||||
### Watch a TXID (called by application)
|
||||
### Watch a TXID (called by your application)
|
||||
|
||||
Used to watch a transaction. Will call the 1-conf callback url after the transaction has been mined. Will call the x-conf callback url after the transaction has x confirmations.
|
||||
|
||||
@@ -209,7 +219,7 @@ Proxy response:
|
||||
}
|
||||
```
|
||||
|
||||
### Get a list of Bitcoin xpub being watched (called by application)
|
||||
### Get a list of Bitcoin xpub being watched (called by your application)
|
||||
|
||||
Returns the list of currently watched xpub and callback information.
|
||||
|
||||
@@ -235,7 +245,7 @@ Proxy response:
|
||||
}
|
||||
```
|
||||
|
||||
### Get a list of Bitcoin addresses being watched by provided xpub (called by application)
|
||||
### Get a list of Bitcoin addresses being watched by provided xpub (called by your application)
|
||||
|
||||
Returns the list of currently watched addresses related to the provided xpub and callback information.
|
||||
|
||||
@@ -262,7 +272,7 @@ Proxy response:
|
||||
}
|
||||
```
|
||||
|
||||
### Get a list of Bitcoin addresses being watched by provided xpub label (called by application)
|
||||
### Get a list of Bitcoin addresses being watched by provided xpub label (called by your application)
|
||||
|
||||
Returns the list of currently watched addresses related to the provided xpub label and callback information.
|
||||
|
||||
@@ -321,7 +331,7 @@ When cyphernode receives a transaction confirmation (/conf endpoint) on a watche
|
||||
"size":371,
|
||||
"vsize":166,
|
||||
"fees":0.00002992,
|
||||
"is_replaceable":0,
|
||||
"replaceable":false,
|
||||
"blockhash":"",
|
||||
"blocktime":"",
|
||||
"blockheight":""
|
||||
@@ -340,7 +350,7 @@ When cyphernode receives a transaction confirmation (/conf endpoint) on a watche
|
||||
"size":371,
|
||||
"vsize":166,
|
||||
"fees":0.00002992,
|
||||
"is_replaceable":0,
|
||||
"replaceable":false,
|
||||
"blockhash":"00000000000000000011bb83bb9bed0f6e131d0d0c903ec3a063e00b3aa00bf6",
|
||||
"blocktime":"2018-10-18T16:58:49+0000",
|
||||
"blockheight":""
|
||||
@@ -367,7 +377,7 @@ Proxy response:
|
||||
}
|
||||
```
|
||||
|
||||
### Get the blockchain information (called by application)
|
||||
### Get the blockchain information (called by your application)
|
||||
|
||||
Returns the blockchain information of the Bitcoin node. Used for example by the welcome app to get syncing progression.
|
||||
|
||||
@@ -431,7 +441,7 @@ Proxy response:
|
||||
}
|
||||
```
|
||||
|
||||
### Get the Block Hash from Height (called by application)
|
||||
### Get the Block Hash from Height (called by your application)
|
||||
|
||||
Returns the best block hash matching height provided.
|
||||
|
||||
@@ -449,7 +459,7 @@ Proxy response:
|
||||
}
|
||||
```
|
||||
|
||||
### Get the Best Block Hash (called by application)
|
||||
### Get the Best Block Hash (called by your application)
|
||||
|
||||
Returns the best block hash of the watching Bitcoin node.
|
||||
|
||||
@@ -467,7 +477,7 @@ Proxy response:
|
||||
}
|
||||
```
|
||||
|
||||
### Get Block Info (called by application)
|
||||
### Get Block Info (called by your application)
|
||||
|
||||
Returns block info for the supplied block hash.
|
||||
|
||||
@@ -506,7 +516,7 @@ Proxy response:
|
||||
}
|
||||
```
|
||||
|
||||
### Get the Best Block Info (called by application)
|
||||
### Get the Best Block Info (called by your application)
|
||||
|
||||
Returns best block info: calls getblockinfo with bestblockhash.
|
||||
|
||||
@@ -545,7 +555,7 @@ Proxy response:
|
||||
}
|
||||
```
|
||||
|
||||
### Get a transaction details (node's getrawtransaction) (called by application)
|
||||
### Get a transaction details (node's getrawtransaction) (called by your application)
|
||||
|
||||
Calls getrawtransaction RPC for the supplied txid.
|
||||
|
||||
@@ -662,7 +672,7 @@ Proxy response:
|
||||
}
|
||||
```
|
||||
|
||||
### Get spending wallet's balance (called by application)
|
||||
### Get spending wallet's balance (called by your application)
|
||||
|
||||
Calls getbalance RPC on the spending wallet.
|
||||
|
||||
@@ -678,7 +688,7 @@ Proxy response:
|
||||
}
|
||||
```
|
||||
|
||||
### Get spending wallet's extended balances (called by application)
|
||||
### Get spending wallet's extended balances (called by your application)
|
||||
|
||||
Calls getbalances RPC on the spending wallet.
|
||||
|
||||
@@ -700,7 +710,7 @@ Proxy response:
|
||||
}
|
||||
```
|
||||
|
||||
### Get a new Bitcoin address from spending wallet (called by application)
|
||||
### Get a new Bitcoin address from spending wallet (called by your application)
|
||||
|
||||
Calls getnewaddress RPC on the spending wallet. Used to refill the spending wallet from cold wallet (ie Trezor). Will derive the default address type (set in your bitcoin.conf file, p2sh-segwit if not specified) or you can supply the address type like the following examples.
|
||||
|
||||
@@ -725,7 +735,7 @@ Proxy response:
|
||||
}
|
||||
```
|
||||
|
||||
### Spend coins from spending wallet (called by application)
|
||||
### Spend coins from spending wallet (called by your application)
|
||||
|
||||
Calls sendtoaddress RPC on the spending wallet with supplied info. Can supply an eventMessage to be published on successful spending. eventMessage should be base64 encoded to avoid dealing with escaping special characters.
|
||||
|
||||
@@ -757,7 +767,7 @@ Proxy response:
|
||||
}
|
||||
```
|
||||
|
||||
### Bump transaction's fees (called by application)
|
||||
### Bump transaction's fees (called by your application)
|
||||
|
||||
Calls bumpfee RPC on the spending wallet with supplied info.
|
||||
|
||||
@@ -780,7 +790,7 @@ Proxy response:
|
||||
}
|
||||
```
|
||||
|
||||
### Add an output to the next batched transaction (called by application)
|
||||
### Add an output to the next batched transaction (called by your application)
|
||||
|
||||
Inserts output information in the DB. Used when batchspend is called later.
|
||||
|
||||
@@ -792,7 +802,7 @@ with body...
|
||||
|
||||
Proxy response: EMPTY
|
||||
|
||||
### Spend a batched transaction with outputs added with addtobatch (called by application)
|
||||
### Spend a batched transaction with outputs added with addtobatch (called by your application)
|
||||
|
||||
Calls sendmany RPC on spending wallet with the unspent "addtobatch" inserted outputs. Will be useful during next bull run.
|
||||
|
||||
@@ -809,7 +819,7 @@ Proxy response:
|
||||
}
|
||||
```
|
||||
|
||||
### Get derived address(es) using path in config and provided index (called by application)
|
||||
### Get derived address(es) using path in config and provided index (called by your application)
|
||||
|
||||
Derives addresses for supplied index. Must be used with derivation.pub32 and derivation.path properties in config.properties.
|
||||
|
||||
@@ -833,7 +843,7 @@ Proxy response:
|
||||
}
|
||||
```
|
||||
|
||||
### Get derived address(es) using provided path and index (called by application)
|
||||
### Get derived address(es) using provided path and index (called by your application)
|
||||
|
||||
Derives addresses for supplied pub32 and path. config.properties' derivation.pub32 and derivation.path are not used.
|
||||
|
||||
@@ -866,7 +876,7 @@ Proxy response:
|
||||
}
|
||||
```
|
||||
|
||||
### Get info from Lightning Network node (called by application)
|
||||
### Get info from Lightning Network node (called by your application)
|
||||
|
||||
Calls getinfo from lightningd. Useful to let your users know where to connect to.
|
||||
|
||||
@@ -901,7 +911,7 @@ Proxy response:
|
||||
}
|
||||
```
|
||||
|
||||
### Create a Lightning Network invoice (called by application)
|
||||
### Create a Lightning Network invoice (called by your application)
|
||||
|
||||
Returns a LN invoice. Label must be unique. Description will be used by your user for payment. Expiry is in seconds and optional. If msatoshi is not supplied, will use "any" (ie donation invoice). callbackUrl is optional.
|
||||
|
||||
@@ -923,7 +933,7 @@ Proxy response:
|
||||
}
|
||||
```
|
||||
|
||||
### Pay a Lightning Network invoice (called by application)
|
||||
### Pay a Lightning Network invoice (called by your 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. If the bolt11 invoice doesn't contain an amount, then the expected_msatoshi supplied here will be used as the paid amount.
|
||||
|
||||
@@ -962,7 +972,7 @@ Proxy response:
|
||||
|
||||
```
|
||||
|
||||
### Get a new Bitcoin address from the Lightning Network node (to fund it) (called by application)
|
||||
### Get a new Bitcoin address from the Lightning Network node (to fund it) (called by your application)
|
||||
|
||||
Returns a Bitcoin bech32 address to fund your LN wallet.
|
||||
|
||||
@@ -1098,7 +1108,7 @@ Proxy response:
|
||||
}
|
||||
```
|
||||
|
||||
### Get the list of peers, with channels, from Lightning Network node (called by application)
|
||||
### Get the list of peers, with channels, from Lightning Network node (called by your application)
|
||||
|
||||
Calls listpeers from lightningd. Returns the list of peers and the channels opened with them, even for currently offline peers.
|
||||
|
||||
@@ -1389,7 +1399,7 @@ Proxy response:
|
||||
"txid": "6b38....b0c3b"
|
||||
}
|
||||
```
|
||||
### Stamp a hash on the Bitcoin blockchain using OTS (called by application)
|
||||
### Stamp a hash on the Bitcoin blockchain using OTS (called by your application)
|
||||
|
||||
Will stamp the supplied hash to the Bitcoin blockchain using OTS. Cyphernode will curl the callback when the OTS stamping is complete.
|
||||
|
||||
@@ -1496,3 +1506,270 @@ Proxy response:
|
||||
"message": "Base64 string of the information text"
|
||||
}
|
||||
```
|
||||
|
||||
### Create a batcher
|
||||
|
||||
Used to create a batching template, by setting a label and a default confTarget.
|
||||
|
||||
```http
|
||||
POST http://cyphernode:8888/createbatcher
|
||||
with body...
|
||||
{"batcherLabel":"lowfees","confTarget":32}
|
||||
```
|
||||
|
||||
Proxy response:
|
||||
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"batcherId": 1
|
||||
},
|
||||
"error": null
|
||||
}
|
||||
```
|
||||
|
||||
### Update a batcher
|
||||
|
||||
Used to change batching template settings.
|
||||
|
||||
```http
|
||||
POST http://cyphernode:8888/updatebatcher
|
||||
with body...
|
||||
{"batcherId":5,"confTarget":12}
|
||||
or
|
||||
{"batcherLabel":"fast","confTarget":2}
|
||||
```
|
||||
|
||||
Proxy response:
|
||||
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"batcherId": 1,
|
||||
"batcherLabel": "default",
|
||||
"confTarget": 6
|
||||
},
|
||||
"error": null
|
||||
}
|
||||
```
|
||||
|
||||
### Add an output to the next batched transaction (called by your 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}
|
||||
or
|
||||
{"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","amount":0.00233,"batcherId":34,"webhookUrl":"https://myCypherApp:3000/batchExecuted"}
|
||||
or
|
||||
{"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","amount":0.00233,"batcherLabel":"lowfees","webhookUrl":"https://myCypherApp:3000/batchExecuted"}
|
||||
or
|
||||
{"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","amount":0.00233,"batcherId":34,"webhookUrl":"https://myCypherApp:3000/batchExecuted"}
|
||||
```
|
||||
|
||||
Proxy response:
|
||||
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"batcherId": 1,
|
||||
"outputId": 34,
|
||||
"nbOutputs": 7,
|
||||
"oldest": "2020-09-09 14:00:01",
|
||||
"total": 0.04016971
|
||||
},
|
||||
"error": null
|
||||
}
|
||||
```
|
||||
|
||||
### Remove an output from the next batched transaction (called by your application)
|
||||
|
||||
Removes a previously added output scheduled for the next batch.
|
||||
|
||||
```http
|
||||
POST http://cyphernode:8888/removefrombatch
|
||||
with body...
|
||||
{"outputId":72}
|
||||
```
|
||||
|
||||
Proxy response:
|
||||
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"batcherId": 1,
|
||||
"outputId": 72,
|
||||
"nbOutputs": 6,
|
||||
"oldest": "2020-09-09 14:00:01",
|
||||
"total": 0.03783971
|
||||
},
|
||||
"error": null
|
||||
}
|
||||
```
|
||||
|
||||
### Spend a batched transaction with outputs previously added with addtobatch (called by your application)
|
||||
|
||||
Calls the sendmany RPC on spending wallet with the unspent "addtobatch" inserted outputs. Will execute default batcher if no batcherId/batcherLabel supplied and default confTarget if no confTarget supplied.
|
||||
|
||||
```http
|
||||
POST http://cyphernode:8888/batchspend
|
||||
with body...
|
||||
{}
|
||||
or
|
||||
{"batcherId":34}
|
||||
or
|
||||
{"batcherId":34,"confTarget":12}
|
||||
or
|
||||
{"batcherLabel":"fastest","confTarget":2}
|
||||
```
|
||||
|
||||
Proxy response:
|
||||
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"batcherId":34,
|
||||
"confTarget":6,
|
||||
"nbOutputs":83,
|
||||
"oldest":123123,
|
||||
"total":10.86990143,
|
||||
"txid":"af867c86000da76df7ddb1054b273ca9e034e8c89d049b5b2795f9f590f67648",
|
||||
"hash":"af867c86000da76df7ddb1054b273ca9e034e8c89d049b5b2795f9f590f67648",
|
||||
"details":{
|
||||
"firstseen":123123,
|
||||
"size":424,
|
||||
"vsize":371,
|
||||
"replaceable":true,
|
||||
"fee":0.00004112
|
||||
},
|
||||
"outputs":{
|
||||
"1abc":0.12,
|
||||
"3abc":0.66,
|
||||
"bc1abc":2.848,
|
||||
...
|
||||
}
|
||||
},
|
||||
"error":null
|
||||
}
|
||||
```
|
||||
|
||||
### Get batcher (called by your application)
|
||||
|
||||
Will return current state/summary of the requested batching template.
|
||||
|
||||
```http
|
||||
POST http://cyphernode:8888/getbatcher
|
||||
with body...
|
||||
{}
|
||||
or
|
||||
{"batcherId":34}
|
||||
or
|
||||
{"batcherLabel":"fastest"}
|
||||
```
|
||||
|
||||
Proxy response:
|
||||
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"batcherId": 1,
|
||||
"batcherLabel": "default",
|
||||
"confTarget": 6,
|
||||
"nbOutputs": 12,
|
||||
"oldest": 123123,
|
||||
"total": 0.86990143
|
||||
},
|
||||
"error": null
|
||||
}
|
||||
```
|
||||
|
||||
### Get batch details (called by your application)
|
||||
|
||||
Will return current state and details of the requested batch, including all outputs. A batch is the combination of a batcher and an optional txid. If no txid is supplied, will return current non-yet-executed batch.
|
||||
|
||||
```http
|
||||
POST http://cyphernode:8888/getbatchdetails
|
||||
with body...
|
||||
{}
|
||||
or
|
||||
{"batcherId":34}
|
||||
or
|
||||
{"batcherLabel":"fastest","txid":"af867c86000da76df7ddb1054b273ca9e034e8c89d049b5b2795f9f590f67648"}
|
||||
```
|
||||
|
||||
Proxy response:
|
||||
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"batcherId": 34,
|
||||
"batcherLabel": "Special batcher for a special client",
|
||||
"confTarget": 6,
|
||||
"nbOutputs": 83,
|
||||
"oldest": 123123,
|
||||
"total": 10.86990143,
|
||||
"txid": "af867c86000da76df7ddb1054b273ca9e034e8c89d049b5b2795f9f590f67648",
|
||||
"hash": "af867c86000da76df7ddb1054b273ca9e034e8c89d049b5b2795f9f590f67648",
|
||||
"details": {
|
||||
"firstseen": 123123,
|
||||
"size": 424,
|
||||
"vsize": 371,
|
||||
"replaceable":true,
|
||||
"fee": 0.00004112
|
||||
},
|
||||
"outputs": {
|
||||
"1abc": 0.12,
|
||||
"3abc": 0.66,
|
||||
"bc1abc": 2.848,
|
||||
...
|
||||
}
|
||||
},
|
||||
"error": null
|
||||
}
|
||||
```
|
||||
|
||||
### Get a list of existing batch templates (called by your application)
|
||||
|
||||
Will return a list of batch templates. batcherId 1 is a default batcher created at installation time.
|
||||
|
||||
```http
|
||||
GET http://cyphernode:8888/listbatchers
|
||||
```
|
||||
|
||||
Proxy response:
|
||||
|
||||
```json
|
||||
{
|
||||
"result": [
|
||||
{"batcherId":1,"batcherLabel":"default","confTarget":6,"nbOutputs":12,"oldest":123123,"total":0.86990143},
|
||||
{"batcherId":2,"batcherLabel":"lowfee","confTarget":32,"nbOutputs":44,"oldest":123123,"total":0.49827387},
|
||||
{"batcherId":3,"batcherLabel":"highfee","confTarget":2,"nbOutputs":7,"oldest":123123,"total":4.16843782}
|
||||
],
|
||||
"error": null
|
||||
}
|
||||
```
|
||||
|
||||
### Get an estimation of current Bitcoin fees
|
||||
|
||||
This will call the Bitcoin Core estimatesmartfee RPC call and return the result as is.
|
||||
|
||||
```http
|
||||
POST http://cyphernode:8888/bitcoin_estimatesmartfee
|
||||
with body...
|
||||
{"confTarget":2}
|
||||
```
|
||||
|
||||
Proxy response:
|
||||
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"feerate": 0.00001000,
|
||||
"blocks": 4
|
||||
},
|
||||
"error": null,
|
||||
"id": null
|
||||
}
|
||||
```
|
||||
|
||||
50
doc/BATCHING.md
Normal file
50
doc/BATCHING.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# How Batching works in Cyphernode
|
||||
|
||||
Details on how batching was implemented in Cyphernode.
|
||||
|
||||
## Glossary
|
||||
|
||||
A Batcher is a batching template with corresponding past batched transactions and a queue of outputs waiting to be batched in the next batch transaction.
|
||||
|
||||
A batched transaction is a transaction that combines multiple recipients in one transaction with multiple outputs, instead of using multiple individual transactions.
|
||||
|
||||
An ongoing batch is a batcher with its queued outputs waiting to be part of the next batch transaction. There's no associated txid yet.
|
||||
|
||||
## Entities
|
||||
|
||||
### Database
|
||||
|
||||
See [Cyphernode's Entity-Relation Model](../proxy_docker/app/data/cyphernode.sql).
|
||||
|
||||
- `batcher`: batching template. The conf_target is the default confTarget that will be used when creating the batch transaction if no confTarget is supplied to batchspend that would override it.
|
||||
- id: autoincrementing primary key
|
||||
- label: optional unique label to be used on subsequent calls instead of using the id
|
||||
- conf_target: optional default confTarget to be used when creating the batched transaction
|
||||
- `recipient`: a batch output. Minimally requires the destination address and the amount.
|
||||
- id: autoincrementing primary key
|
||||
- address: destination Bitcoin address
|
||||
- amount: amount to be sent, in BTC
|
||||
- tx_id: foreign key on the tx table, the actual transaction if created
|
||||
- webhook_url: optional URL that you want Cyphernode to call back when the batch transaction is broadcast
|
||||
- batcher_id: foreign key on the batcher table, the corresponding batching template for this recipient
|
||||
- label: an optional label for this output.
|
||||
- `tx`: a transaction. The information about a broadcast Bitcoin transaction.
|
||||
|
||||
### Good to know
|
||||
|
||||
- There is a default batcher created on installation time, with id 1, label "default" and conf_target 6.
|
||||
- When a recipient has no tx_id, it means it is waiting for the next batch.
|
||||
- When a recipian has a batcher_id, it means it is part of a past or ongoing batch.
|
||||
- When a batch transaction is broadcast, the webhook_url of each included recipient will be called by Cyphernode, if present, with information about the batched transaction in the POSTed body.
|
||||
- Cyphernode knows when a callback webhook didn't work. It will retry the callback when a new blocks is mined, until it works.
|
||||
|
||||
## 2nd layer: the Batcher cypherapp
|
||||
|
||||
The Cyphernode's base functionalities for batching is pretty basic. Instead of adding complex batching features to the Cyphernode API, we decided to develop a [CypherApp](CYPHERAPPS.md).
|
||||
|
||||
The [Batcher](https://github.com/SatoshiPortal/batcher) cypherapp will take care of the following tasks:
|
||||
|
||||
- Merging same destination outputs into one, adding the amounts and calling the different webhook URLs on batch execution.
|
||||
- Scheduling the batches.
|
||||
- Executing the batches when an amount threshold has been reached.
|
||||
- Hiding Cyphernode complexity by dealing only with a `batchRequestId` and a `batchId`.
|
||||
BIN
doc/CN-Arch.jpg
BIN
doc/CN-Arch.jpg
Binary file not shown.
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 85 KiB |
@@ -146,6 +146,61 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ApiResponseTemporarilyUnavailable'
|
||||
/unwatch:
|
||||
post:
|
||||
tags:
|
||||
- "watching addresses"
|
||||
- "core features"
|
||||
summary: "Stop watching a Bitcoin address"
|
||||
description: "Updates the watched Bitcoin address row in DB so that callbacks won't be called on tx confirmations for that address for the specified URLs or id."
|
||||
operationId: "deleteWatchedAddress"
|
||||
requestBody:
|
||||
description: "Bitcoin address that needs to be watched"
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: "object"
|
||||
properties:
|
||||
address:
|
||||
$ref: '#/components/schemas/TypeAddressString'
|
||||
unconfirmedCallbackURL:
|
||||
type: "string"
|
||||
format: "url"
|
||||
confirmedCallbackURL:
|
||||
type: "string"
|
||||
format: "url"
|
||||
id:
|
||||
description: "id returned by the corresponding watch"
|
||||
type: "string"
|
||||
responses:
|
||||
'200':
|
||||
description: "successfully unwatched"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: "object"
|
||||
properties:
|
||||
event:
|
||||
type: "string"
|
||||
address:
|
||||
$ref: '#/components/schemas/TypeAddressString'
|
||||
unconfirmedCallbackURL:
|
||||
type: "string"
|
||||
format: "url"
|
||||
confirmedCallbackURL:
|
||||
type: "string"
|
||||
format: "url"
|
||||
'400':
|
||||
$ref: '#/components/schemas/ApiResponseInvalidInput'
|
||||
'403':
|
||||
$ref: '#/components/schemas/ApiResponseNotAllowed'
|
||||
'503':
|
||||
description: "Resource temporarily unavailable"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ApiResponseTemporarilyUnavailable'
|
||||
/watchxpub:
|
||||
post:
|
||||
tags:
|
||||
@@ -1181,16 +1236,115 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ApiResponseTemporarilyUnavailable'
|
||||
/createbatcher:
|
||||
post:
|
||||
tags:
|
||||
- "spending wallet"
|
||||
- "core features"
|
||||
- "batching"
|
||||
summary: "Create a batching template, by setting a label and a default confTarget"
|
||||
description: "Inserts batcher information to the DB."
|
||||
operationId: "createbatcher"
|
||||
requestBody:
|
||||
description: "Batcher label and conf target"
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: "object"
|
||||
properties:
|
||||
batcherLabel:
|
||||
type: "string"
|
||||
confTarget:
|
||||
type: "number"
|
||||
responses:
|
||||
'200':
|
||||
description: "operation successful"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: "object"
|
||||
properties:
|
||||
result:
|
||||
type: "object"
|
||||
properties:
|
||||
batcherId:
|
||||
type: "number"
|
||||
error:
|
||||
type: "object"
|
||||
'403':
|
||||
$ref: '#/components/schemas/ApiResponseNotAllowed'
|
||||
'405':
|
||||
$ref: '#/components/schemas/ApiResponseInvalidInput'
|
||||
'503':
|
||||
description: "Resource temporarily unavailable"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ApiResponseTemporarilyUnavailable'
|
||||
/updatebatcher:
|
||||
post:
|
||||
tags:
|
||||
- "spending wallet"
|
||||
- "core features"
|
||||
- "batching"
|
||||
summary: "Update a batching template, by changing the label or the default confTarget"
|
||||
description: "Updates batcher information to the DB."
|
||||
operationId: "updatebatcher"
|
||||
requestBody:
|
||||
description: "Batcher id, batcher label and conf target"
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: "object"
|
||||
properties:
|
||||
batcherId:
|
||||
type: "number"
|
||||
batcherLabel:
|
||||
type: "string"
|
||||
confTarget:
|
||||
type: "number"
|
||||
responses:
|
||||
'200':
|
||||
description: "operation successful"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: "object"
|
||||
properties:
|
||||
result:
|
||||
type: "object"
|
||||
properties:
|
||||
batcherId:
|
||||
type: "number"
|
||||
batcherLabel:
|
||||
type: "string"
|
||||
confTarget:
|
||||
type: "number"
|
||||
error:
|
||||
type: "object"
|
||||
'403':
|
||||
$ref: '#/components/schemas/ApiResponseNotAllowed'
|
||||
'405':
|
||||
$ref: '#/components/schemas/ApiResponseInvalidInput'
|
||||
'503':
|
||||
description: "Resource temporarily unavailable"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ApiResponseTemporarilyUnavailable'
|
||||
/addtobatch:
|
||||
post:
|
||||
tags:
|
||||
- "spending wallet"
|
||||
- "core features"
|
||||
- "batching"
|
||||
summary: "Adds spending of some amount to some address to the next batch"
|
||||
description: "Inserts output information in the DB. Used when batchspend is called later."
|
||||
operationId: "spendInNextBatch"
|
||||
requestBody:
|
||||
description: "Address and amount"
|
||||
description: "Address, amount, batcherId, batcherLabel and webhookUrl"
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
@@ -1204,9 +1358,90 @@ paths:
|
||||
$ref: '#/components/schemas/TypeAddressString'
|
||||
amount:
|
||||
type: "number"
|
||||
batcherId:
|
||||
type: "number"
|
||||
batcherLabel:
|
||||
type: "string"
|
||||
webhookUrl:
|
||||
type: "string"
|
||||
format: "url"
|
||||
responses:
|
||||
'200':
|
||||
description: "operation successful"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: "object"
|
||||
properties:
|
||||
result:
|
||||
type: "object"
|
||||
properties:
|
||||
batcherId:
|
||||
type: "number"
|
||||
outputId:
|
||||
type: "number"
|
||||
nbOutputs:
|
||||
type: "number"
|
||||
oldest:
|
||||
type: "string"
|
||||
total:
|
||||
type: "number"
|
||||
error:
|
||||
type: "object"
|
||||
'403':
|
||||
$ref: '#/components/schemas/ApiResponseNotAllowed'
|
||||
'405':
|
||||
$ref: '#/components/schemas/ApiResponseInvalidInput'
|
||||
'503':
|
||||
description: "Resource temporarily unavailable"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ApiResponseTemporarilyUnavailable'
|
||||
/removefrombatch:
|
||||
post:
|
||||
tags:
|
||||
- "spending wallet"
|
||||
- "core features"
|
||||
- "batching"
|
||||
summary: "Removes a previously added output from the next batch"
|
||||
description: "Deletes output from the DB."
|
||||
operationId: "removeFromNextBatch"
|
||||
requestBody:
|
||||
description: "outputId returned by corresponding addtobatch"
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: "object"
|
||||
required:
|
||||
- "outputId"
|
||||
properties:
|
||||
outputId:
|
||||
type: "number"
|
||||
responses:
|
||||
'200':
|
||||
description: "operation successful"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: "object"
|
||||
properties:
|
||||
result:
|
||||
type: "object"
|
||||
properties:
|
||||
batcherId:
|
||||
type: "number"
|
||||
outputId:
|
||||
type: "number"
|
||||
nbOutputs:
|
||||
type: "number"
|
||||
oldest:
|
||||
type: "string"
|
||||
total:
|
||||
type: "number"
|
||||
error:
|
||||
type: "object"
|
||||
'403':
|
||||
$ref: '#/components/schemas/ApiResponseNotAllowed'
|
||||
'405':
|
||||
@@ -1218,13 +1453,28 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ApiResponseTemporarilyUnavailable'
|
||||
/batchspend:
|
||||
get:
|
||||
post:
|
||||
tags:
|
||||
- "spending wallet"
|
||||
- "core features"
|
||||
summary: "Spend previously amounts/addresses added with addtobatch"
|
||||
description: "Creates a batched transaction whose outputs are the previously unspent addtobatch calls."
|
||||
- "batching"
|
||||
summary: "Spend previously added amounts/addresses in a batch"
|
||||
description: "Creates a batched transaction whose outputs are the previously unspent addtobatch calls for the batcher."
|
||||
operationId: "batchSpend"
|
||||
requestBody:
|
||||
description: "batcherId or batcherLabel with an optional confTarget to override the batcher's default"
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: "object"
|
||||
properties:
|
||||
batcherId:
|
||||
type: "number"
|
||||
batcherLabel:
|
||||
type: "string"
|
||||
confTarget:
|
||||
type: "number"
|
||||
responses:
|
||||
'200':
|
||||
description: "operation successful"
|
||||
@@ -1232,18 +1482,232 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
type: "object"
|
||||
required:
|
||||
- "status"
|
||||
- "hash"
|
||||
properties:
|
||||
status:
|
||||
type: "string"
|
||||
hash:
|
||||
$ref: '#/components/schemas/TypeHashString'
|
||||
'400':
|
||||
$ref: '#/components/schemas/ApiResponseInvalidInput'
|
||||
result:
|
||||
type: "object"
|
||||
properties:
|
||||
batcherId:
|
||||
type: "number"
|
||||
confTarget:
|
||||
type: "number"
|
||||
nbOutputs:
|
||||
type: "number"
|
||||
oldest:
|
||||
type: "string"
|
||||
total:
|
||||
type: "number"
|
||||
txid:
|
||||
type: "string"
|
||||
hash:
|
||||
type: "string"
|
||||
details:
|
||||
type: "object"
|
||||
outputs:
|
||||
type: "object"
|
||||
error:
|
||||
type: "object"
|
||||
'403':
|
||||
$ref: '#/components/schemas/ApiResponseNotAllowed'
|
||||
'405':
|
||||
$ref: '#/components/schemas/ApiResponseInvalidInput'
|
||||
'503':
|
||||
description: "Resource temporarily unavailable"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ApiResponseTemporarilyUnavailable'
|
||||
/getbatcher:
|
||||
post:
|
||||
tags:
|
||||
- "spending wallet"
|
||||
- "core features"
|
||||
- "batching"
|
||||
summary: "Returns current state/summary of the requested batching template"
|
||||
description: "Get information from the batcher and recipient DB tables."
|
||||
operationId: "getBatcher"
|
||||
requestBody:
|
||||
description: "Optional batcherId or batcherLabel, default batcher if not supplied"
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: "object"
|
||||
properties:
|
||||
batcherId:
|
||||
type: "number"
|
||||
batcherLabel:
|
||||
type: "string"
|
||||
responses:
|
||||
'200':
|
||||
description: "operation successful"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: "object"
|
||||
properties:
|
||||
result:
|
||||
type: "object"
|
||||
properties:
|
||||
batcherId:
|
||||
type: "number"
|
||||
batcherLabel:
|
||||
type: "string"
|
||||
confTarget:
|
||||
type: "number"
|
||||
nbOutputs:
|
||||
type: "number"
|
||||
oldest:
|
||||
type: "string"
|
||||
total:
|
||||
type: "number"
|
||||
error:
|
||||
type: "object"
|
||||
'403':
|
||||
$ref: '#/components/schemas/ApiResponseNotAllowed'
|
||||
'405':
|
||||
$ref: '#/components/schemas/ApiResponseInvalidInput'
|
||||
'503':
|
||||
description: "Resource temporarily unavailable"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ApiResponseTemporarilyUnavailable'
|
||||
/getbatchdetails:
|
||||
post:
|
||||
tags:
|
||||
- "spending wallet"
|
||||
- "core features"
|
||||
- "batching"
|
||||
summary: "Returns current state and details of the requested batch, including all outputs"
|
||||
description: "Get detailed information from the batcher and recipient DB tables."
|
||||
operationId: "getBatcherDetails"
|
||||
requestBody:
|
||||
description: "Optional batcherId or batcherLabel and txid, default batcher if not supplied"
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: "object"
|
||||
properties:
|
||||
batcherId:
|
||||
type: "number"
|
||||
batcherLabel:
|
||||
type: "string"
|
||||
txid:
|
||||
type: "string"
|
||||
responses:
|
||||
'200':
|
||||
description: "operation successful"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: "object"
|
||||
properties:
|
||||
result:
|
||||
type: "object"
|
||||
properties:
|
||||
batcherId:
|
||||
type: "number"
|
||||
batcherLabel:
|
||||
type: "string"
|
||||
confTarget:
|
||||
type: "number"
|
||||
nbOutputs:
|
||||
type: "number"
|
||||
oldest:
|
||||
type: "string"
|
||||
total:
|
||||
type: "number"
|
||||
txid:
|
||||
type: "string"
|
||||
hash:
|
||||
type: "string"
|
||||
details:
|
||||
type: "object"
|
||||
outputs:
|
||||
type: "object"
|
||||
error:
|
||||
type: "object"
|
||||
'403':
|
||||
$ref: '#/components/schemas/ApiResponseNotAllowed'
|
||||
'405':
|
||||
$ref: '#/components/schemas/ApiResponseInvalidInput'
|
||||
'503':
|
||||
description: "Resource temporarily unavailable"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ApiResponseTemporarilyUnavailable'
|
||||
/listbatchers:
|
||||
get:
|
||||
tags:
|
||||
- "spending wallet"
|
||||
- "core features"
|
||||
- "batching"
|
||||
summary: "Get list of batchers, including the default batcher"
|
||||
description: "Returns the list of batch templates."
|
||||
operationId: "listBatchers"
|
||||
responses:
|
||||
'200':
|
||||
description: "successful operation"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: "object"
|
||||
properties:
|
||||
result:
|
||||
type: "array"
|
||||
error:
|
||||
type: "object"
|
||||
'403':
|
||||
$ref: '#/components/schemas/ApiResponseNotAllowed'
|
||||
'503':
|
||||
description: "Resource temporarily unavailable"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ApiResponseTemporarilyUnavailable'
|
||||
/bitcoin_estimatesmartfee:
|
||||
post:
|
||||
tags:
|
||||
- "core features"
|
||||
- "bitcoin"
|
||||
summary: "Returns current fee estimation computed by Bitcoin Core's estimatesmartfee"
|
||||
description: "Returns current fee estimation computed by Bitcoin Core's estimatesmartfee"
|
||||
operationId: "estimateSmartFee"
|
||||
requestBody:
|
||||
description: "Conf Target"
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: "object"
|
||||
properties:
|
||||
confTarget:
|
||||
type: "number"
|
||||
responses:
|
||||
'200':
|
||||
description: "operation successful"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: "object"
|
||||
properties:
|
||||
result:
|
||||
type: "object"
|
||||
properties:
|
||||
feerate:
|
||||
type: "number"
|
||||
blocks:
|
||||
type: "number"
|
||||
error:
|
||||
type: "object"
|
||||
id:
|
||||
type: "number"
|
||||
'403':
|
||||
$ref: '#/components/schemas/ApiResponseNotAllowed'
|
||||
'405':
|
||||
$ref: '#/components/schemas/ApiResponseInvalidInput'
|
||||
'503':
|
||||
description: "Resource temporarily unavailable"
|
||||
content:
|
||||
|
||||
@@ -116,7 +116,7 @@ components:
|
||||
type: "integer"
|
||||
fees:
|
||||
type: "number"
|
||||
is_replaceable:
|
||||
replaceable:
|
||||
type: "integer"
|
||||
blockhash:
|
||||
$ref: '#/components/schemas/TypeHashString'
|
||||
|
||||
@@ -14,7 +14,7 @@ CREATE TABLE watching_by_pub32 (
|
||||
|
||||
CREATE TABLE watching (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
address TEXT UNIQUE,
|
||||
address TEXT,
|
||||
watching INTEGER DEFAULT FALSE,
|
||||
callback0conf TEXT,
|
||||
calledback0conf INTEGER DEFAULT FALSE,
|
||||
@@ -26,6 +26,8 @@ CREATE TABLE watching (
|
||||
event_message TEXT,
|
||||
inserted_ts INTEGER DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE INDEX idx_watching_address ON watching (address);
|
||||
CREATE UNIQUE INDEX idx_watching_01 ON watching (address, callback0conf, callback1conf);
|
||||
|
||||
CREATE TABLE watching_tx (
|
||||
watching_id INTEGER REFERENCES watching,
|
||||
@@ -48,6 +50,7 @@ CREATE TABLE tx (
|
||||
blockhash TEXT,
|
||||
blockheight INTEGER,
|
||||
blocktime INTEGER,
|
||||
conf_target INTEGER,
|
||||
raw_tx TEXT,
|
||||
inserted_ts INTEGER DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
@@ -64,13 +67,28 @@ CREATE TABLE recipient (
|
||||
address TEXT,
|
||||
amount REAL,
|
||||
tx_id INTEGER REFERENCES tx,
|
||||
inserted_ts INTEGER DEFAULT CURRENT_TIMESTAMP
|
||||
inserted_ts INTEGER DEFAULT CURRENT_TIMESTAMP,
|
||||
webhook_url TEXT,
|
||||
calledback INTEGER DEFAULT FALSE,
|
||||
calledback_ts INTEGER,
|
||||
batcher_id INTEGER REFERENCES batcher,
|
||||
label TEXT
|
||||
);
|
||||
CREATE INDEX idx_recipient_address ON recipient (address);
|
||||
CREATE INDEX idx_recipient_label ON recipient (label);
|
||||
|
||||
CREATE TABLE batcher (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
label TEXT UNIQUE,
|
||||
conf_target INTEGER,
|
||||
feerate REAL,
|
||||
inserted_ts INTEGER DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
INSERT INTO batcher (id, label, conf_target, feerate) VALUES (1, "default", 6, NULL);
|
||||
|
||||
CREATE TABLE watching_by_txid (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
txid TEXT UNIQUE,
|
||||
txid TEXT,
|
||||
watching INTEGER DEFAULT FALSE,
|
||||
callback1conf TEXT,
|
||||
calledback1conf INTEGER DEFAULT FALSE,
|
||||
@@ -80,6 +98,7 @@ CREATE TABLE watching_by_txid (
|
||||
inserted_ts INTEGER DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE INDEX idx_watching_by_txid_txid ON watching_by_txid (txid);
|
||||
CREATE UNIQUE INDEX idx_watching_by_txid_1x ON watching_by_txid (txid, callback1conf, callbackxconf);
|
||||
|
||||
CREATE TABLE stamp (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
||||
0
proxy_docker/app/data/sqlmigrate20181213_0-0.1.sh
Normal file → Executable file
0
proxy_docker/app/data/sqlmigrate20181213_0-0.1.sh
Normal file → Executable file
0
proxy_docker/app/data/sqlmigrate20190104_0.1-0.2.sh
Normal file → Executable file
0
proxy_docker/app/data/sqlmigrate20190104_0.1-0.2.sh
Normal file → Executable file
0
proxy_docker/app/data/sqlmigrate20190130_0.1-0.2.sh
Normal file → Executable file
0
proxy_docker/app/data/sqlmigrate20190130_0.1-0.2.sh
Normal file → Executable file
0
proxy_docker/app/data/sqlmigrate20191127_0.2.4-0.3.0.sh
Normal file → Executable file
0
proxy_docker/app/data/sqlmigrate20191127_0.2.4-0.3.0.sh
Normal file → Executable file
14
proxy_docker/app/data/sqlmigrate20200610_0.4.0-0.5.0.sh
Executable file
14
proxy_docker/app/data/sqlmigrate20200610_0.4.0-0.5.0.sh
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo "Checking for extended batching support in DB..."
|
||||
count=$(sqlite3 $DB_FILE "select count(*) from pragma_table_info('recipient') where name='batcher_id'")
|
||||
if [ "${count}" -eq "0" ]; then
|
||||
# batcher_id not there, we have to migrate
|
||||
echo "Migrating database for extended batching support..."
|
||||
echo "Backing up current DB..."
|
||||
cp $DB_FILE $DB_FILE-sqlmigrate20200610_0.4.0-0.5.0
|
||||
echo "Altering DB..."
|
||||
cat sqlmigrate20200610_0.4.0-0.5.0.sql | sqlite3 $DB_FILE
|
||||
else
|
||||
echo "Database extended batching support migration already done, skipping!"
|
||||
fi
|
||||
75
proxy_docker/app/data/sqlmigrate20200610_0.4.0-0.5.0.sql
Normal file
75
proxy_docker/app/data/sqlmigrate20200610_0.4.0-0.5.0.sql
Normal file
@@ -0,0 +1,75 @@
|
||||
PRAGMA foreign_keys=off;
|
||||
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
CREATE TABLE batcher (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
label TEXT UNIQUE,
|
||||
conf_target INTEGER,
|
||||
feerate REAL,
|
||||
inserted_ts INTEGER DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
INSERT INTO batcher (id, label, conf_target, feerate) VALUES (1, "default", 6, NULL);
|
||||
|
||||
ALTER TABLE recipient ADD COLUMN webhook_url TEXT;
|
||||
ALTER TABLE recipient ADD COLUMN batcher_id INTEGER REFERENCES batcher;
|
||||
ALTER TABLE recipient ADD COLUMN label INTEGER REFERENCES batcher;
|
||||
ALTER TABLE recipient ADD COLUMN calledback INTEGER DEFAULT FALSE;
|
||||
ALTER TABLE recipient ADD COLUMN calledback_ts INTEGER;
|
||||
CREATE INDEX idx_recipient_label ON recipient (label);
|
||||
|
||||
ALTER TABLE tx ADD COLUMN conf_target INTEGER DEFAULT NULL;
|
||||
|
||||
|
||||
ALTER TABLE watching RENAME TO watching_20200610;
|
||||
|
||||
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,
|
||||
watching_by_pub32_id INTEGER REFERENCES watching_by_pub32,
|
||||
pub32_index INTEGER,
|
||||
event_message TEXT,
|
||||
inserted_ts INTEGER DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
INSERT INTO watching SELECT * FROM watching_20200610;
|
||||
|
||||
DROP INDEX IF EXISTS idx_watching_address;
|
||||
CREATE INDEX idx_watching_address ON watching (address);
|
||||
DROP INDEX IF EXISTS idx_watching_01;
|
||||
CREATE UNIQUE INDEX idx_watching_01 ON watching (address, callback0conf, callback1conf);
|
||||
|
||||
--DROP TABLE watching20200610;
|
||||
|
||||
ALTER TABLE watching_by_txid RENAME TO watching_by_txid_20200610;
|
||||
|
||||
CREATE TABLE watching_by_txid (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
txid TEXT,
|
||||
watching INTEGER DEFAULT FALSE,
|
||||
callback1conf TEXT,
|
||||
calledback1conf INTEGER DEFAULT FALSE,
|
||||
callbackxconf TEXT,
|
||||
calledbackxconf INTEGER DEFAULT FALSE,
|
||||
nbxconf INTEGER,
|
||||
inserted_ts INTEGER DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
INSERT INTO watching_by_txid SELECT * FROM watching_by_txid_20200610;
|
||||
|
||||
DROP INDEX IF EXISTS idx_watching_by_txid_txid;
|
||||
CREATE INDEX idx_watching_by_txid_txid ON watching_by_txid (txid);
|
||||
DROP INDEX IF EXISTS idx_watching_by_txid_1x;
|
||||
CREATE UNIQUE INDEX idx_watching_by_txid_1x ON watching_by_txid (txid, callback1conf, callbackxconf);
|
||||
|
||||
--DROP TABLE watching_by_txid_20200610;
|
||||
|
||||
COMMIT;
|
||||
|
||||
PRAGMA foreign_keys=on;
|
||||
880
proxy_docker/app/script/batching.sh
Normal file
880
proxy_docker/app/script/batching.sh
Normal file
@@ -0,0 +1,880 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./trace.sh
|
||||
. ./sendtobitcoinnode.sh
|
||||
|
||||
createbatcher() {
|
||||
trace "Entering createbatcher()..."
|
||||
|
||||
# POST http://192.168.111.152:8080/createbatcher
|
||||
#
|
||||
# args:
|
||||
# - batcherLabel, optional, id can be used to reference the batcher
|
||||
# - confTarget, optional, overriden by batchspend's confTarget, default Bitcoin Core conf_target will be used if not supplied
|
||||
# NOTYET - feeRate, sat/vB, optional, overrides confTarget if supplied, overriden by batchspend's feeRate, default Bitcoin Core fee policy will be used if not supplied
|
||||
#
|
||||
# response:
|
||||
# - batcherId, the batcher id
|
||||
#
|
||||
# BODY {"batcherLabel":"lowfees","confTarget":32}
|
||||
# NOTYET BODY {"batcherLabel":"highfees","feeRate":231.8}
|
||||
|
||||
local request=${1}
|
||||
local response
|
||||
local label=$(echo "${request}" | jq ".batcherLabel")
|
||||
trace "[createbatcher] label=${label}"
|
||||
local conf_target=$(echo "${request}" | jq ".confTarget")
|
||||
trace "[createbatcher] conf_target=${conf_target}"
|
||||
local feerate=$(echo "${request}" | jq ".feeRate")
|
||||
trace "[createbatcher] feerate=${feerate}"
|
||||
|
||||
# if [ "${feerate}" != "null" ]; then
|
||||
# # If not null, let's nullify conf_target since feerate overrides it
|
||||
# conf_target="null"
|
||||
# trace "[createbatcher] Overriding conf_target=${conf_target}"
|
||||
# fi
|
||||
|
||||
local batcher_id
|
||||
|
||||
batcher_id=$(sql "INSERT OR IGNORE INTO batcher (label, conf_target, feerate) VALUES (${label}, ${conf_target}, ${feerate}); SELECT LAST_INSERT_ROWID();")
|
||||
|
||||
if ("${batcher_id}" -eq "0"); then
|
||||
trace "[createbatcher] Could not insert"
|
||||
response='{"result":null,"error":{"code":-32700,"message":"Could not create batcher, label probably already exists","data":'${request}'}}'
|
||||
else
|
||||
trace "[createbatcher] Inserted"
|
||||
response='{"result":{"batcherId":'${batcher_id}'},"error":null}'
|
||||
fi
|
||||
|
||||
echo "${response}"
|
||||
}
|
||||
|
||||
updatebatcher() {
|
||||
trace "Entering updatebatcher()..."
|
||||
|
||||
# POST http://192.168.111.152:8080/updatebatcher
|
||||
#
|
||||
# args:
|
||||
# - batcherId, optional, batcher id to update, will update default batcher if not supplied
|
||||
# - batcherLabel, optional, id can be used to reference the batcher, will update default batcher if not supplied, if id is present then change the label with supplied text
|
||||
# - confTarget, optional, new confirmation target for the batcher
|
||||
# NOTYET - feeRate, sat/vB, optional, new feerate for the batcher
|
||||
#
|
||||
# response:
|
||||
# - batcherId, the batcher id
|
||||
# - batcherLabel, the batcher label
|
||||
# - confTarget, the batcher default confirmation target
|
||||
# NOTYET - feeRate, the batcher default feerate
|
||||
#
|
||||
# BODY {"batcherId":5,"confTarget":12}
|
||||
# NOTYET BODY {"batcherLabel":"highfees","feeRate":400}
|
||||
# NOTYET BODY {"batcherId":3,"batcherLabel":"ultrahighfees","feeRate":800}
|
||||
# BODY {"batcherLabel":"fast","confTarget":2}
|
||||
|
||||
local request=${1}
|
||||
local response
|
||||
local whereclause
|
||||
local returncode
|
||||
|
||||
local id=$(echo "${request}" | jq ".batcherId")
|
||||
trace "[updatebatcher] id=${id}"
|
||||
local label=$(echo "${request}" | jq ".batcherLabel")
|
||||
trace "[updatebatcher] label=${label}"
|
||||
local conf_target=$(echo "${request}" | jq ".confTarget")
|
||||
trace "[updatebatcher] conf_target=${conf_target}"
|
||||
local feerate=$(echo "${request}" | jq ".feeRate")
|
||||
trace "[updatebatcher] feerate=${feerate}"
|
||||
|
||||
if [ "${id}" = "null" ] && [ "${label}" = "null" ]; then
|
||||
# If id and label are null, use default batcher
|
||||
trace "[updatebatcher] Using default batcher 1"
|
||||
id=1
|
||||
fi
|
||||
|
||||
# if [ "${feerate}" != "null" ]; then
|
||||
# # If not null, let's nullify conf_target since feerate overrides it
|
||||
# conf_target="null"
|
||||
# trace "[updatebatcher] Overriding conf_target=${conf_target}"
|
||||
# fi
|
||||
|
||||
if [ "${id}" = "null" ]; then
|
||||
whereclause="label=${label}"
|
||||
else
|
||||
whereclause="id = ${id}"
|
||||
fi
|
||||
|
||||
sql "UPDATE batcher set label=${label}, conf_target=${conf_target}, feerate=${feerate} WHERE ${whereclause}"
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
if [ "${returncode}" -ne 0 ]; then
|
||||
response='{"result":null,"error":{"code":-32700,"message":"Could not update batcher","data":'${request}'}}'
|
||||
else
|
||||
response='{"result":{"batcherId":'${id}'},"error":null}'
|
||||
fi
|
||||
|
||||
echo "${response}"
|
||||
}
|
||||
|
||||
addtobatch() {
|
||||
trace "Entering addtobatch()..."
|
||||
|
||||
# POST http://192.168.111.152:8080/addtobatch
|
||||
#
|
||||
# args:
|
||||
# - address, required, desination address
|
||||
# - amount, required, amount to send to the destination address
|
||||
# - outputLabel, optional, if you want to reference this output
|
||||
# - batcherId, optional, the id of the batcher to which the output will be added, default batcher if not supplied, overrides batcherLabel
|
||||
# - batcherLabel, optional, the label of the batcher to which the output will be added, default batcher if not supplied
|
||||
# - webhookUrl, optional, the webhook to call when the batch is broadcast
|
||||
#
|
||||
# response:
|
||||
# - batcherId, the id of the batcher
|
||||
# - outputId, the id of the added output
|
||||
# - nbOutputs, the number of outputs currently in the batch
|
||||
# - oldest, the timestamp of the oldest output in the batch
|
||||
# - total, the current sum of the batch's output amounts
|
||||
#
|
||||
# BODY {"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","amount":0.00233}
|
||||
# BODY {"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","amount":0.00233,"batcherId":34,"webhookUrl":"https://myCypherApp:3000/batchExecuted"}
|
||||
# BODY {"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","amount":0.00233,"batcherLabel":"lowfees","webhookUrl":"https://myCypherApp:3000/batchExecuted"}
|
||||
# BODY {"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","amount":0.00233,"batcherId":34,"webhookUrl":"https://myCypherApp:3000/batchExecuted"}
|
||||
|
||||
local request=${1}
|
||||
local response
|
||||
local returncode=0
|
||||
local inserted_id
|
||||
local row
|
||||
|
||||
local address=$(echo "${request}" | jq -r ".address")
|
||||
trace "[addtobatch] address=${address}"
|
||||
local amount=$(echo "${request}" | jq ".amount")
|
||||
trace "[addtobatch] amount=${amount}"
|
||||
local label=$(echo "${request}" | jq ".outputLabel")
|
||||
trace "[addtobatch] label=${label}"
|
||||
local batcher_id=$(echo "${request}" | jq ".batcherId")
|
||||
trace "[addtobatch] batcher_id=${batcher_id}"
|
||||
local batcher_label=$(echo "${request}" | jq ".batcherLabel")
|
||||
trace "[addtobatch] batcher_label=${batcher_label}"
|
||||
local webhook_url=$(echo "${request}" | jq ".webhookUrl")
|
||||
trace "[addtobatch] webhook_url=${webhook_url}"
|
||||
|
||||
local isvalid
|
||||
isvalid=$(validateaddress "${address}" | jq ".result.isvalid")
|
||||
if [ "${isvalid}" != "true" ]; then
|
||||
|
||||
response='{"result":null,"error":{"code":-32700,"message":"Invalid address","data":'${request}'}}'
|
||||
|
||||
trace "[addtobatch] Invalid address"
|
||||
trace "[addtobatch] responding=${response}"
|
||||
|
||||
echo "${response}"
|
||||
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ "${batcher_id}" = "null" ] && [ "${batcher_label}" = "null" ]; then
|
||||
# If batcher_id and batcher_label are null, use default batcher
|
||||
trace "[addtobatch] Using default batcher 1"
|
||||
batcher_id=1
|
||||
fi
|
||||
|
||||
if [ "${batcher_id}" = "null" ]; then
|
||||
# Using batcher_label
|
||||
batcher_id=$(sql "SELECT id FROM batcher WHERE label=${batcher_label}")
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
fi
|
||||
|
||||
if [ -z "${batcher_id}" ]; then
|
||||
# batcherLabel not found
|
||||
response='{"result":null,"error":{"code":-32700,"message":"batcher not found","data":'${request}'}}'
|
||||
else
|
||||
# Check if address already pending for this batcher...
|
||||
inserted_id=$(sql "SELECT id FROM recipient WHERE address=\"${address}\" AND tx_id IS NULL AND batcher_id=${batcher_id}")
|
||||
if [ -n "${inserted_id}" ]; then
|
||||
response='{"result":null,"error":{"code":-32700,"message":"Duplicated address","data":'${request}'}}'
|
||||
|
||||
trace "[addtobatch] Duplicated address"
|
||||
trace "[addtobatch] responding=${response}"
|
||||
|
||||
echo "${response}"
|
||||
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Insert the new destination
|
||||
inserted_id=$(sql "INSERT INTO recipient (address, amount, webhook_url, batcher_id, label) VALUES (\"${address}\", ${amount}, ${webhook_url}, ${batcher_id}, ${label}); SELECT LAST_INSERT_ROWID();")
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
|
||||
if [ "${returncode}" -ne 0 ]; then
|
||||
response='{"result":null,"error":{"code":-32700,"message":"Could not add to batch","data":'${request}'}}'
|
||||
else
|
||||
row=$(sql "SELECT COUNT(id), MIN(inserted_ts), SUM(amount) FROM recipient WHERE tx_id IS NULL AND batcher_id=${batcher_id}")
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
|
||||
local count=$(echo "${row}" | cut -d '|' -f1)
|
||||
trace "[addtobatch] count=${count}"
|
||||
local oldest=$(echo "${row}" | cut -d '|' -f2)
|
||||
trace "[addtobatch] oldest=${oldest}"
|
||||
local total=$(echo "${row}" | cut -d '|' -f3)
|
||||
trace "[addtobatch] total=${total}"
|
||||
|
||||
response='{"result":{"batcherId":'${batcher_id}',"outputId":'${inserted_id}',"nbOutputs":'${count}',"oldest":"'${oldest}'","total":'${total}'},"error":null}'
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "${response}"
|
||||
}
|
||||
|
||||
removefrombatch() {
|
||||
trace "Entering removefrombatch()..."
|
||||
|
||||
# POST http://192.168.111.152:8080/removefrombatch
|
||||
#
|
||||
# args:
|
||||
# - outputId, required, id of the output to remove
|
||||
#
|
||||
# response:
|
||||
# - batcherId, the id of the batcher
|
||||
# - outputId, the id of the removed output if found
|
||||
# - nbOutputs, the number of outputs currently in the batch
|
||||
# - oldest, the timestamp of the oldest output in the batch
|
||||
# - total, the current sum of the batch's output amounts
|
||||
#
|
||||
# BODY {"id":72}
|
||||
|
||||
local request=${1}
|
||||
local response
|
||||
local returncode=0
|
||||
local row
|
||||
local batcher_id
|
||||
|
||||
local id=$(echo "${request}" | jq ".outputId")
|
||||
trace "[removefrombatch] id=${id}"
|
||||
|
||||
if [ "${id}" = "null" ]; then
|
||||
# id is required
|
||||
trace "[removefrombatch] id missing"
|
||||
response='{"result":null,"error":{"code":-32700,"message":"outputId is required","data":'${request}'}}'
|
||||
else
|
||||
batcher_id=$(sql "SELECT batcher_id FROM recipient WHERE id=${id}")
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
|
||||
if [ -n "${batcher_id}" ]; then
|
||||
sql "DELETE FROM recipient WHERE id=${id}"
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
|
||||
if [ "${returncode}" -ne 0 ]; then
|
||||
response='{"result":null,"error":{"code":-32700,"message":"Output was not removed","data":'${request}'}}'
|
||||
else
|
||||
row=$(sql "SELECT COUNT(id), COALESCE(MIN(inserted_ts), 0), COALESCE(SUM(amount), 0.00000000) FROM recipient WHERE tx_id IS NULL AND batcher_id=${batcher_id}")
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
|
||||
local count=$(echo "${row}" | cut -d '|' -f1)
|
||||
trace "[removefrombatch] count=${count}"
|
||||
local oldest=$(echo "${row}" | cut -d '|' -f2)
|
||||
trace "[removefrombatch] oldest=${oldest}"
|
||||
local total=$(echo "${row}" | cut -d '|' -f3)
|
||||
trace "[removefrombatch] total=${total}"
|
||||
|
||||
response='{"result":{"batcherId":'${batcher_id}',"outputId":'${id}',"nbOutputs":'${count}',"oldest":"'${oldest}'","total":'${total}'},"error":null}'
|
||||
fi
|
||||
else
|
||||
response='{"result":null,"error":{"code":-32700,"message":"Output not found or already spent","data":'${request}'}}'
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "${response}"
|
||||
}
|
||||
|
||||
batchspend() {
|
||||
trace "Entering batchspend()..."
|
||||
|
||||
# POST http://192.168.111.152:8080/batchspend
|
||||
#
|
||||
# args:
|
||||
# - batcherId, optional, id of the batcher to execute, overrides batcherLabel, default batcher will be spent if not supplied
|
||||
# - batcherLabel, optional, label of the batcher to execute, default batcher will be executed if not supplied
|
||||
# - confTarget, optional, overrides default value of createbatcher, default to value of createbatcher, default Bitcoin Core conf_target will be used if not supplied
|
||||
# NOTYET - feeRate, optional, overrides confTarget if supplied, overrides default value of createbatcher, default to value of createbatcher, default Bitcoin Core value will be used if not supplied
|
||||
#
|
||||
# response:
|
||||
# - batcherId, id of the executed batcher
|
||||
# - confTarget, conf_target used for the spend
|
||||
# - nbOutputs, the number of outputs spent in the batch
|
||||
# - oldest, the timestamp of the oldest output in the spent batch
|
||||
# - total, the sum of the spent batch's output amounts
|
||||
# - txid, the batch transaction id
|
||||
# - hash, the transaction hash
|
||||
# - tx details: firstseen, size, vsize, replaceable, fee
|
||||
# - outputs
|
||||
#
|
||||
# BODY {}
|
||||
# BODY {"batcherId":"34","confTarget":12}
|
||||
# NOTYET BODY {"batcherLabel":"highfees","feeRate":233.7}
|
||||
# BODY {"batcherId":"411","confTarget":6}
|
||||
|
||||
local request=${1}
|
||||
local response
|
||||
local returncode=0
|
||||
local row
|
||||
local whereclause
|
||||
|
||||
local batcher_id=$(echo "${request}" | jq ".batcherId")
|
||||
trace "[batchspend] batcher_id=${batcher_id}"
|
||||
local batcher_label=$(echo "${request}" | jq ".batcherLabel")
|
||||
trace "[batchspend] batcher_label=${batcher_label}"
|
||||
local conf_target=$(echo "${request}" | jq ".confTarget")
|
||||
trace "[batchspend] conf_target=${conf_target}"
|
||||
local feerate=$(echo "${request}" | jq ".feeRate")
|
||||
trace "[batchspend] feerate=${feerate}"
|
||||
|
||||
if [ "${batcher_id}" = "null" ] && [ "${batcher_label}" = "null" ]; then
|
||||
# If batcher_id and batcher_label are null, use default batcher
|
||||
trace "[batchspend] Using default batcher 1"
|
||||
batcher_id=1
|
||||
fi
|
||||
|
||||
if [ "${batcher_id}" = "null" ]; then
|
||||
# Using batcher_label
|
||||
whereclause="label=${batcher_label}"
|
||||
else
|
||||
whereclause="id=${batcher_id}"
|
||||
fi
|
||||
|
||||
local batcher=$(sql "SELECT id, conf_target, feerate FROM batcher WHERE ${whereclause}")
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
|
||||
if [ -z "${batcher}" ]; then
|
||||
# batcherLabel not found
|
||||
response='{"result":null,"error":{"code":-32700,"message":"batcher not found","data":'${request}'}}'
|
||||
else
|
||||
# All good, let's try to batch spend!
|
||||
|
||||
# NOTYET
|
||||
# We'll use supplied feerate
|
||||
# If not supplied, we'll use supplied conf_target
|
||||
# If not supplied, we'll use batcher default feerate
|
||||
# If not set, we'll use batcher default conf_target
|
||||
# If not set, default Bitcoin Core fee policy will be used
|
||||
|
||||
# We'll use the supplied conf_target
|
||||
# If not supplied, we'll use the batcher default conf_target
|
||||
# If not set, default Bitcoin Core fee policy will be used
|
||||
|
||||
# if [ "${feerate}" != "null" ]; then
|
||||
# # If not null, let's nullify conf_target since feerate overrides it
|
||||
# conf_target=
|
||||
# trace "[batchspend] Overriding conf_target=${conf_target}"
|
||||
# else
|
||||
# if [ "${conf_target}" = "null" ]; then
|
||||
# feerate=$(echo "${batcher}" | cut -d '|' -f3)
|
||||
# if [ -z "${feerate}" ]; then
|
||||
# # If null, let's use batcher conf_target
|
||||
# conf_target=$(echo "${batcher}" | cut -d '|' -f2)
|
||||
# fi
|
||||
# fi
|
||||
# fi
|
||||
|
||||
if [ "${conf_target}" = "null" ]; then
|
||||
conf_target=$(echo "${batcher}" | cut -d '|' -f2)
|
||||
trace "[batchspend] Using batcher default conf_target=${conf_target}"
|
||||
fi
|
||||
|
||||
batcher_id=$(echo "${batcher}" | cut -d '|' -f1)
|
||||
|
||||
local batching=$(sql "SELECT address, amount, id, webhook_url FROM recipient WHERE tx_id IS NULL AND batcher_id=${batcher_id}")
|
||||
trace "[batchspend] batching=${batching}"
|
||||
|
||||
local data
|
||||
local recipientsjson
|
||||
local webhooks_data
|
||||
local id_inserted
|
||||
local tx_details
|
||||
local tx_raw_details
|
||||
local address
|
||||
local amount
|
||||
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}"
|
||||
recipient_id=$(echo "${row}" | cut -d '|' -f3)
|
||||
trace "[batchspend] recipient_id=${recipient_id}"
|
||||
webhook_url=$(echo "${row}" | cut -d '|' -f4)
|
||||
trace "[batchspend] webhook_url=${webhook_url}"
|
||||
|
||||
if [ -z "${recipientsjson}" ]; then
|
||||
whereclause="\"${recipient_id}\""
|
||||
recipientsjson="\"${address}\":${amount}"
|
||||
webhooks_data="{\"outputId\":${recipient_id},\"address\":\"${address}\",\"amount\":${amount},\"webhookUrl\":\"${webhook_url}\"}"
|
||||
else
|
||||
whereclause="${whereclause},\"${recipient_id}\""
|
||||
recipientsjson="${recipientsjson},\"${address}\":${amount}"
|
||||
webhooks_data="${webhooks_data},{\"outputId\":${recipient_id},\"address\":\"${address}\",\"amount\":${amount},\"webhookUrl\":\"${webhook_url}\"}"
|
||||
fi
|
||||
done
|
||||
|
||||
local bitcoincore_args="{\"method\":\"sendmany\",\"params\":[\"\", {${recipientsjson}}"
|
||||
if [ -n "${conf_target}" ]; then
|
||||
bitcoincore_args="${bitcoincore_args}, 1, \"\", null, null, ${conf_target}"
|
||||
fi
|
||||
bitcoincore_args="${bitcoincore_args}]}"
|
||||
data=$(send_to_spender_node "${bitcoincore_args}")
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
trace "[batchspend] data=${data}"
|
||||
|
||||
if [ "${returncode}" -eq 0 ]; then
|
||||
local txid=$(echo "${data}" | jq -r ".result")
|
||||
trace "[batchspend] txid=${txid}"
|
||||
|
||||
# Let's get transaction details on the spending wallet so that we have fee information
|
||||
tx_details=$(get_transaction ${txid} "spender")
|
||||
tx_raw_details=$(get_rawtransaction ${txid} | tr -d '\n')
|
||||
|
||||
# Amounts and fees are negative when spending so we absolute those fields
|
||||
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 | fabs' | awk '{ printf "%.8f", $0 }')
|
||||
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 -r '.result."bip125-replaceable"')
|
||||
trace "[batchspend] tx_replaceable=${tx_replaceable}"
|
||||
tx_replaceable=$([ "${tx_replaceable}" = "yes" ] && echo "true" || echo "false")
|
||||
trace "[batchspend] tx_replaceable=${tx_replaceable}"
|
||||
local fees=$(echo "${tx_details}" | jq '.result.fee | fabs' | awk '{ printf "%.8f", $0 }')
|
||||
# Sometimes raw tx are too long to be passed as paramater, so let's write
|
||||
# it to a temp file for it to be read by sqlite3 and then delete the file
|
||||
echo "${tx_raw_details}" > batchspend-rawtx-${txid}-$$.blob
|
||||
|
||||
# Get the info on the batch before setting it to done
|
||||
row=$(sql "SELECT COUNT(id), COALESCE(MIN(inserted_ts), 0), COALESCE(SUM(amount), 0.00000000) FROM recipient WHERE tx_id IS NULL AND batcher_id=${batcher_id}")
|
||||
|
||||
# Let's insert the txid in our little DB -- then we'll already have it when receiving confirmation
|
||||
id_inserted=$(sql "INSERT OR IGNORE INTO tx (txid, hash, confirmations, timereceived, fee, size, vsize, is_replaceable, conf_target, raw_tx) VALUES (\"${txid}\", ${tx_hash}, 0, ${tx_ts_firstseen}, ${fees}, ${tx_size}, ${tx_vsize}, ${tx_replaceable}, ${conf_target}, readfile('batchspend-rawtx-${txid}-$$.blob')); SELECT LAST_INSERT_ROWID();")
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
if [ "${returncode}" -eq 0 ]; then
|
||||
if [ "${id_inserted}" -eq 0 ]; then
|
||||
id_inserted=$(sql "SELECT id FROM tx WHERE txid=\"${txid}\"")
|
||||
fi
|
||||
trace "[batchspend] id_inserted: ${id_inserted}"
|
||||
sql "UPDATE recipient SET tx_id=${id_inserted} WHERE id IN (${whereclause})"
|
||||
trace_rc $?
|
||||
fi
|
||||
|
||||
# Use the selected row above (before the insert)
|
||||
local count=$(echo "${row}" | cut -d '|' -f1)
|
||||
trace "[batchspend] count=${count}"
|
||||
local oldest=$(echo "${row}" | cut -d '|' -f2)
|
||||
trace "[batchspend] oldest=${oldest}"
|
||||
local total=$(echo "${row}" | cut -d '|' -f3)
|
||||
trace "[batchspend] total=${total}"
|
||||
|
||||
response='{"result":{"batcherId":'${batcher_id}',"confTarget":'${conf_target}',"nbOutputs":'${count}',"oldest":"'${oldest}'","total":'${total}
|
||||
response="${response},\"status\":\"accepted\",\"txid\":\"${txid}\",\"hash\":${tx_hash},\"details\":{\"firstseen\":${tx_ts_firstseen},\"size\":${tx_size},\"vsize\":${tx_vsize},\"replaceable\":${tx_replaceable},\"fee\":${fees}},\"outputs\":[${webhooks_data}]}"
|
||||
response="${response},\"error\":null}"
|
||||
|
||||
# Delete the temp file containing the raw tx (see above)
|
||||
rm batchspend-rawtx-${txid}-$$.blob
|
||||
|
||||
batch_webhooks "[${webhooks_data}]" '"batcherId":'${batcher_id}',"confTarget":'${conf_target}',"nbOutputs":'${count}',"oldest":"'${oldest}'","total":'${total}',"status":"accepted","txid":"'${txid}'","hash":'${tx_hash}',"details":{"firstseen":'${tx_ts_firstseen}',"size":'${tx_size}',"vsize":'${tx_vsize}',"replaceable":'${tx_replaceable}',"fee":'${fees}'}'
|
||||
|
||||
else
|
||||
local message=$(echo "${data}" | jq -e ".error.message")
|
||||
response='{"result":null,"error":{"code":-32700,"message":'${message}',"data":'${request}'}}'
|
||||
fi
|
||||
fi
|
||||
|
||||
trace "[batchspend] responding=${response}"
|
||||
echo "${response}"
|
||||
}
|
||||
|
||||
batch_check_webhooks() {
|
||||
trace "Entering batch_check_webhooks()..."
|
||||
|
||||
local webhooks_data
|
||||
local address
|
||||
local amount
|
||||
local recipient_id
|
||||
local webhook_url
|
||||
local batcher_id
|
||||
local txid
|
||||
local tx_hash
|
||||
local tx_ts_firstseen
|
||||
local tx_size
|
||||
local tx_vsize
|
||||
local tx_replaceable
|
||||
local fees
|
||||
local conf_target
|
||||
local row
|
||||
local count
|
||||
local oldest
|
||||
local total
|
||||
local tx_id
|
||||
|
||||
local batching=$(sql "SELECT address, amount, r.id, webhook_url, b.id, t.txid, t.hash, t.timereceived, t.fee, t.size, t.vsize, t.is_replaceable, t.conf_target, t.id FROM recipient r, batcher b, tx t WHERE r.batcher_id=b.id AND r.tx_id=t.id AND NOT calledback AND tx_id IS NOT NULL AND webhook_url IS NOT NULL")
|
||||
trace "[batch_check_webhooks] batching=${batching}"
|
||||
|
||||
local IFS=$'\n'
|
||||
for row in ${batching}
|
||||
do
|
||||
trace "[batch_check_webhooks] row=${row}"
|
||||
address=$(echo "${row}" | cut -d '|' -f1)
|
||||
trace "[batch_check_webhooks] address=${address}"
|
||||
amount=$(echo "${row}" | cut -d '|' -f2)
|
||||
trace "[batch_check_webhooks] amount=${amount}"
|
||||
recipient_id=$(echo "${row}" | cut -d '|' -f3)
|
||||
trace "[batch_check_webhooks] recipient_id=${recipient_id}"
|
||||
webhook_url=$(echo "${row}" | cut -d '|' -f4)
|
||||
trace "[batch_check_webhooks] webhook_url=${webhook_url}"
|
||||
batcher_id=$(echo "${row}" | cut -d '|' -f5)
|
||||
trace "[batch_check_webhooks] batcher_id=${batcher_id}"
|
||||
txid=$(echo "${row}" | cut -d '|' -f6)
|
||||
trace "[batch_check_webhooks] txid=${txid}"
|
||||
tx_hash=$(echo "${row}" | cut -d '|' -f7)
|
||||
trace "[batch_check_webhooks] tx_hash=${tx_hash}"
|
||||
tx_ts_firstseen=$(echo "${row}" | cut -d '|' -f8)
|
||||
trace "[batch_check_webhooks] tx_ts_firstseen=${tx_ts_firstseen}"
|
||||
fees=$(echo "${row}" | cut -d '|' -f9)
|
||||
trace "[batch_check_webhooks] fees=${fees}"
|
||||
tx_size=$(echo "${row}" | cut -d '|' -f10)
|
||||
trace "[batch_check_webhooks] tx_size=${tx_size}"
|
||||
tx_vsize=$(echo "${row}" | cut -d '|' -f11)
|
||||
trace "[batch_check_webhooks] tx_vsize=${tx_vsize}"
|
||||
tx_replaceable=$(echo "${row}" | cut -d '|' -f12)
|
||||
tx_replaceable=$([ "${tx_replaceable}" -eq "1" ] && echo "true" || echo "false")
|
||||
trace "[batch_check_webhooks] tx_replaceable=${tx_replaceable}"
|
||||
conf_target=$(echo "${row}" | cut -d '|' -f13)
|
||||
trace "[batch_check_webhooks] conf_target=${conf_target}"
|
||||
tx_id=$(echo "${row}" | cut -d '|' -f14)
|
||||
trace "[batch_check_webhooks] tx_id=${tx_id}"
|
||||
|
||||
webhooks_data="{\"outputId\":${recipient_id},\"address\":\"${address}\",\"amount\":${amount},\"webhookUrl\":\"${webhook_url}\"}"
|
||||
|
||||
# I know this query for each output is not very efficient, but this function should not execute often, only in case of
|
||||
# failed callbacks on batches...
|
||||
# Get the info on the batch
|
||||
row=$(sql "SELECT COUNT(id), COALESCE(MIN(inserted_ts), 0), COALESCE(SUM(amount), 0.00000000) FROM recipient r WHERE tx_id=\"${tx_id}\"")
|
||||
|
||||
# Use the selected row above
|
||||
count=$(echo "${row}" | cut -d '|' -f1)
|
||||
trace "[batchspend] count=${count}"
|
||||
oldest=$(echo "${row}" | cut -d '|' -f2)
|
||||
trace "[batchspend] oldest=${oldest}"
|
||||
total=$(echo "${row}" | cut -d '|' -f3)
|
||||
trace "[batchspend] total=${total}"
|
||||
|
||||
batch_webhooks "[${webhooks_data}]" '"batcherId":'${batcher_id}',"confTarget":'${conf_target}',"nbOutputs":'${count}',"oldest":"'${oldest}'","total":'${total}',"status":"accepted","txid":"'${txid}'","hash":"'${tx_hash}'","details":{"firstseen":'${tx_ts_firstseen}',"size":'${tx_size}',"vsize":'${tx_vsize}',"replaceable":'${tx_replaceable}',"fee":'${fees}'}'
|
||||
done
|
||||
}
|
||||
|
||||
batch_webhooks() {
|
||||
trace "Entering batch_webhooks()..."
|
||||
|
||||
# webhooks_data:
|
||||
# {"outputId":1,"address":"1abc","amount":0.12,"webhookUrl":"https://bleah.com/batchwebhook"}"
|
||||
local webhooks_data=${1}
|
||||
trace "[batch_webhooks] webhooks_data=${webhooks_data}"
|
||||
|
||||
# tx:
|
||||
# {"batcherId":1,"txid":"abc123","hash":"abc123","details":{"firstseen":123123,"size":200,"vsize":141,"replaceable":true,"fee":0.00001}}'
|
||||
local tx=${2}
|
||||
trace "[batch_webhooks] tx=${tx}"
|
||||
|
||||
local outputs
|
||||
local output_id
|
||||
local address
|
||||
local amount
|
||||
local webhook_url
|
||||
local body
|
||||
local successful_recipient_ids
|
||||
local returncode
|
||||
|
||||
outputs=$(echo "${webhooks_data}" | jq -Mc ".[]")
|
||||
|
||||
local output
|
||||
local IFS=$'\n'
|
||||
for output in ${outputs}
|
||||
do
|
||||
webhook_url=$(echo "${output}" | jq -r ".webhookUrl")
|
||||
trace "[batch_webhooks] webhook_url=${webhook_url}"
|
||||
|
||||
if [ -z "${webhook_url}" ] || [ "${webhook_url}" = "null" ]; then
|
||||
trace "[batch_webhooks] Empty webhook_url, skipping"
|
||||
continue
|
||||
fi
|
||||
|
||||
output_id=$(echo "${output}" | jq ".outputId")
|
||||
trace "[batch_webhooks] output_id=${output_id}"
|
||||
address=$(echo "${output}" | jq ".address")
|
||||
trace "[batch_webhooks] address=${address}"
|
||||
amount=$(echo "${output}" | jq ".amount")
|
||||
trace "[batch_webhooks] amount=${amount}"
|
||||
|
||||
body='{"outputId":'${output_id}',"address":'${address}',"amount":'${amount}','${tx}'}'
|
||||
trace "[batch_webhooks] body=${body}"
|
||||
|
||||
notify_web "${webhook_url}" "${body}" ${TOR_ADDR_WATCH_WEBHOOKS}
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
|
||||
if [ "${returncode}" -eq 0 ]; then
|
||||
if [ -n "${successful_recipient_ids}" ]; then
|
||||
successful_recipient_ids="${successful_recipient_ids},${output_id}"
|
||||
else
|
||||
successful_recipient_ids="${output_id}"
|
||||
fi
|
||||
else
|
||||
trace "[batch_webhooks] callback failed, won't set to true in DB"
|
||||
fi
|
||||
done
|
||||
|
||||
sql "UPDATE recipient SET calledback=1, calledback_ts=CURRENT_TIMESTAMP WHERE id IN (${successful_recipient_ids})"
|
||||
trace_rc $?
|
||||
}
|
||||
|
||||
listbatchers() {
|
||||
trace "Entering listbatchers()..."
|
||||
|
||||
# curl (GET) http://192.168.111.152:8080/listbatchers
|
||||
#
|
||||
# {"result":[
|
||||
# {"batcherId":1,"batcherLabel":"default","confTarget":6,"nbOutputs":12,"oldest":123123,"total":0.86990143},
|
||||
# {"batcherId":2,"batcherLabel":"lowfee","confTarget":32,"nbOutputs":44,"oldest":123123,"total":0.49827387},
|
||||
# {"batcherId":3,"batcherLabel":"highfee","confTarget":2,"nbOutputs":7,"oldest":123123,"total":4.16843782}
|
||||
# ],
|
||||
# "error":null}
|
||||
|
||||
|
||||
local batchers=$(sql "SELECT b.id, '{\"batcherId\":' || b.id || ',\"batcherLabel\":\"' || b.label || '\",\"confTarget\":' || conf_target || ',\"nbOutputs\":' || COUNT(r.id) || ',\"oldest\":\"' ||COALESCE(MIN(r.inserted_ts), 0) || '\",\"total\":' ||COALESCE(SUM(amount), 0.00000000) || '}' FROM batcher b LEFT JOIN recipient r ON r.batcher_id=b.id AND r.tx_id IS NULL GROUP BY b.id")
|
||||
trace "[listbatchers] batchers=${batchers}"
|
||||
|
||||
local returncode
|
||||
local response
|
||||
local batcher
|
||||
local jsonstring
|
||||
local IFS=$'\n'
|
||||
for batcher in ${batchers}
|
||||
do
|
||||
jsonstring=$(echo ${batcher} | cut -d '|' -f2)
|
||||
if [ -z "${response}" ]; then
|
||||
response='{"result":['${jsonstring}
|
||||
else
|
||||
response="${response},${jsonstring}"
|
||||
fi
|
||||
done
|
||||
|
||||
response=${response}'],"error":null}'
|
||||
trace "[listbatchers] responding=${response}"
|
||||
echo "${response}"
|
||||
}
|
||||
|
||||
getbatcher() {
|
||||
trace "Entering getbatcher()..."
|
||||
|
||||
# POST (GET) http://192.168.111.152:8080/getbatcher
|
||||
#
|
||||
# args:
|
||||
# - batcherId, optional, id of the batcher, overrides batcerhLabel, default batcher will be used if not supplied
|
||||
# - batcherLabel, optional, label of the batcher, default batcher will be used if not supplied
|
||||
#
|
||||
# response:
|
||||
# {"result":{"batcherId":1,"batcherLabel":"default","confTarget":6,"nbOutputs":12,"oldest":123123,"total":0.86990143},"error":null}
|
||||
#
|
||||
# BODY {}
|
||||
# BODY {"batcherId":34}
|
||||
|
||||
local request=${1}
|
||||
local response
|
||||
local returncode=0
|
||||
local batcher
|
||||
local whereclause
|
||||
|
||||
local batcher_id=$(echo "${request}" | jq ".batcherId")
|
||||
trace "[getbatcher] batcher_id=${batcher_id}"
|
||||
local batcher_label=$(echo "${request}" | jq ".batcherLabel")
|
||||
trace "[getbatcher] batcher_label=${batcher_label}"
|
||||
|
||||
if [ "${batcher_id}" = "null" ] && [ "${batcher_label}" = "null" ]; then
|
||||
# If batcher_id and batcher_label are null, use default batcher
|
||||
trace "[getbatcher] Using default batcher 1"
|
||||
batcher_id=1
|
||||
fi
|
||||
|
||||
if [ "${batcher_id}" = "null" ]; then
|
||||
# Using batcher_label
|
||||
whereclause="b.label=${batcher_label}"
|
||||
else
|
||||
# Using batcher_id
|
||||
whereclause="b.id=${batcher_id}"
|
||||
fi
|
||||
|
||||
batcher=$(sql "SELECT b.id, '{\"batcherId\":' || b.id || ',\"batcherLabel\":\"' || b.label || '\",\"confTarget\":' || conf_target || ',\"nbOutputs\":' || COUNT(r.id) || ',\"oldest\":\"' ||COALESCE(MIN(r.inserted_ts), 0) || '\",\"total\":' ||COALESCE(SUM(amount), 0.00000000) || '}' FROM batcher b LEFT JOIN recipient r ON r.batcher_id=b.id AND r.tx_id IS NULL WHERE ${whereclause} GROUP BY b.id")
|
||||
trace "[getbatcher] batcher=${batcher}"
|
||||
|
||||
if [ -n "${batcher}" ]; then
|
||||
batcher=$(echo "${batcher}" | cut -d '|' -f2)
|
||||
response='{"result":'${batcher}',"error":null}'
|
||||
else
|
||||
response='{"result":null,"error":{"code":-32700,"message":"batcher not found","data":'${request}'}}'
|
||||
fi
|
||||
|
||||
echo "${response}"
|
||||
}
|
||||
|
||||
getbatchdetails() {
|
||||
trace "Entering getbatchdetails()..."
|
||||
|
||||
# POST (GET) http://192.168.111.152:8080/getbatchdetails
|
||||
#
|
||||
# args:
|
||||
# - batcherId, optional, id of the batcher, overrides batcherLabel, default batcher will be used if not supplied
|
||||
# - batcherLabel, optional, label of the batcher, default batcher will be used if not supplied
|
||||
# - txid, optional, if you want the details of an executed batch, supply the batch txid, will return current pending batch
|
||||
# if not supplied
|
||||
#
|
||||
# response:
|
||||
# {"result":{
|
||||
# "batcherId":34,
|
||||
# "batcherLabel":"Special batcher for a special client",
|
||||
# "confTarget":6,
|
||||
# "nbOutputs":83,
|
||||
# "oldest":123123,
|
||||
# "total":10.86990143,
|
||||
# "txid":"af867c86000da76df7ddb1054b273ca9e034e8c89d049b5b2795f9f590f67648",
|
||||
# "hash":"af867c86000da76df7ddb1054b273ca9e034e8c89d049b5b2795f9f590f67648",
|
||||
# "details":{
|
||||
# "firstseen":123123,
|
||||
# "size":424,
|
||||
# "vsize":371,
|
||||
# "replaceable":yes,
|
||||
# "fee":0.00004112
|
||||
# },
|
||||
# "outputs":[
|
||||
# "1abc":0.12,
|
||||
# "3abc":0.66,
|
||||
# "bc1abc":2.848,
|
||||
# ...
|
||||
# ]
|
||||
# }
|
||||
# },"error":null}
|
||||
#
|
||||
# BODY {}
|
||||
# BODY {"batcherId":34}
|
||||
|
||||
local request=${1}
|
||||
local response
|
||||
local returncode=0
|
||||
local batch
|
||||
local tx
|
||||
local outputsjson
|
||||
local whereclause
|
||||
|
||||
local batcher_id=$(echo "${request}" | jq ".batcherId")
|
||||
trace "[getbatchdetails] batcher_id=${batcher_id}"
|
||||
local batcher_label=$(echo "${request}" | jq ".batcherLabel")
|
||||
trace "[getbatchdetails] batcher_label=${batcher_label}"
|
||||
local txid=$(echo "${request}" | jq ".txid")
|
||||
trace "[getbatchdetails] txid=${txid}"
|
||||
|
||||
if [ "${batcher_id}" = "null" ] && [ "${batcher_label}" = "null" ]; then
|
||||
# If batcher_id and batcher_label are null, use default batcher
|
||||
trace "[getbatchdetails] Using default batcher 1"
|
||||
batcher_id=1
|
||||
fi
|
||||
|
||||
if [ "${batcher_id}" = "null" ]; then
|
||||
# Using batcher_label
|
||||
whereclause="b.label=${batcher_label}"
|
||||
else
|
||||
# Using batcher_id
|
||||
whereclause="b.id=${batcher_id}"
|
||||
fi
|
||||
|
||||
if [ "${txid}" != "null" ]; then
|
||||
# Using txid
|
||||
whereclause="${whereclause} AND t.txid=${txid}"
|
||||
else
|
||||
# null txid
|
||||
whereclause="${whereclause} AND t.txid IS NULL"
|
||||
outerclause="AND r.tx_id IS NULL"
|
||||
fi
|
||||
|
||||
# First get the batch summary
|
||||
batch=$(sql "SELECT b.id, COALESCE(t.id, NULL), '{\"batcherId\":' || b.id || ',\"batcherLabel\":\"' || b.label || '\",\"confTarget\":' || b.conf_target || ',\"nbOutputs\":' || COUNT(r.id) || ',\"oldest\":\"' ||COALESCE(MIN(r.inserted_ts), 0) || '\",\"total\":' ||COALESCE(SUM(amount), 0.00000000) FROM batcher b LEFT JOIN recipient r ON r.batcher_id=b.id ${outerclause} LEFT JOIN tx t ON t.id=r.tx_id WHERE ${whereclause} GROUP BY b.id")
|
||||
trace "[getbatchdetails] batch=${batch}"
|
||||
|
||||
if [ -n "${batch}" ]; then
|
||||
local tx_id
|
||||
local outputs
|
||||
|
||||
tx_id=$(echo "${batch}" | cut -d '|' -f2)
|
||||
trace "[getbatchdetails] tx_id=${tx_id}"
|
||||
if [ -n "${tx_id}" ]; then
|
||||
# Using txid
|
||||
outerclause="AND r.tx_id=${tx_id}"
|
||||
|
||||
tx=$(sql "SELECT '\"txid\":\"' || txid || '\",\"hash\":\"' || hash || '\",\"details\":{\"firstseen\":' || timereceived || ',\"size\":' || size || ',\"vsize\":' || vsize || ',\"replaceable\":' || CASE is_replaceable WHEN 1 THEN 'true' ELSE 'false' END || ',\"fee\":' || fee || '}' FROM tx WHERE id=${tx_id}")
|
||||
else
|
||||
# null txid
|
||||
outerclause="AND r.tx_id IS NULL"
|
||||
fi
|
||||
|
||||
batcher_id=$(echo "${batch}" | cut -d '|' -f1)
|
||||
outputs=$(sql "SELECT '{\"outputId\":' || id || ',\"outputLabel\":\"' || COALESCE(label, '') || '\",\"address\":\"' || address || '\",\"amount\":' || amount || ',\"addedTimestamp\":\"' || inserted_ts || '\"}' FROM recipient r WHERE batcher_id=${batcher_id} ${outerclause}")
|
||||
|
||||
local output
|
||||
local IFS=$'\n'
|
||||
for output in ${outputs}
|
||||
do
|
||||
if [ -n "${outputsjson}" ]; then
|
||||
outputsjson="${outputsjson},${output}"
|
||||
else
|
||||
outputsjson="${output}"
|
||||
fi
|
||||
done
|
||||
|
||||
batch=$(echo "${batch}" | cut -d '|' -f3)
|
||||
|
||||
response='{"result":'${batch}
|
||||
if [ -n "${tx}" ]; then
|
||||
response=${response}','${tx}
|
||||
else
|
||||
response=${response}',"txid":null,"hash":null'
|
||||
fi
|
||||
response=${response}',"outputs":['${outputsjson}']},"error":null}'
|
||||
else
|
||||
response='{"result":null,"error":{"code":-32700,"message":"batch not found or no corresponding txid","data":'${request}'}}'
|
||||
fi
|
||||
|
||||
echo "${response}"
|
||||
|
||||
}
|
||||
|
||||
# curl localhost:8888/listbatchers | jq
|
||||
# curl -d '{}' localhost:8888/getbatcher | jq
|
||||
# curl -d '{}' localhost:8888/getbatchdetails | jq
|
||||
# curl -d '{"outputLabel":"test002","address":"1abd","amount":0.0002}' localhost:8888/addtobatch | jq
|
||||
# curl -d '{}' localhost:8888/batchspend | jq
|
||||
# curl -d '{"outputId":1}' localhost:8888/removefrombatch | jq
|
||||
|
||||
# curl -d '{"batcherLabel":"lowfees","confTarget":32}' localhost:8888/createbatcher | jq
|
||||
# curl localhost:8888/listbatchers | jq
|
||||
|
||||
# curl -d '{"batcherLabel":"lowfees"}' localhost:8888/getbatcher | jq
|
||||
# curl -d '{"batcherLabel":"lowfees"}' localhost:8888/getbatchdetails | jq
|
||||
# curl -d '{"batcherLabel":"lowfees","outputLabel":"test002","address":"1abd","amount":0.0002}' localhost:8888/addtobatch | jq
|
||||
# curl -d '{"batcherLabel":"lowfees"}' localhost:8888/batchspend | jq
|
||||
# curl -d '{"batcherLabel":"lowfees","outputId":9}' localhost:8888/removefrombatch | jq
|
||||
@@ -103,3 +103,14 @@ validateaddress() {
|
||||
send_to_watcher_node "${data}"
|
||||
return $?
|
||||
}
|
||||
|
||||
bitcoin_estimatesmartfee() {
|
||||
trace "Entering bitcoin_estimatesmartfee()..."
|
||||
|
||||
local conf_target=${1}
|
||||
trace "[bitcoin_estimatesmartfee] conf_target=${conf_target}"
|
||||
local data="{\"method\":\"estimatesmartfee\",\"params\":[${conf_target}]}"
|
||||
trace "[bitcoin_estimatesmartfee] data=${data}"
|
||||
send_to_watcher_node "${data}"
|
||||
return $?
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ do_callbacks() {
|
||||
trace "Entering do_callbacks()..."
|
||||
|
||||
# Let's fetch all the watching addresses still being watched but not called back
|
||||
local callbacks=$(sql 'SELECT DISTINCT w.callback0conf, address, txid, vout, amount, confirmations, timereceived, fee, size, vsize, blockhash, blockheight, blocktime, w.id, is_replaceable, pub32_index, pub32, label, derivation_path, event_message FROM watching w LEFT JOIN watching_tx ON w.id = watching_id LEFT JOIN tx ON tx.id = tx_id LEFT JOIN watching_by_pub32 w32 ON watching_by_pub32_id = w32.id WHERE NOT calledback0conf AND watching_id NOT NULL AND w.callback0conf NOT NULL AND w.watching')
|
||||
local callbacks=$(sql 'SELECT DISTINCT w.callback0conf, address, txid, vout, amount, confirmations, timereceived, fee, size, vsize, blockhash, blockheight, blocktime, w.id, is_replaceable, pub32_index, pub32, label, derivation_path, event_message, hash FROM watching w LEFT JOIN watching_tx ON w.id = watching_id LEFT JOIN tx ON tx.id = tx_id LEFT JOIN watching_by_pub32 w32 ON watching_by_pub32_id = w32.id WHERE NOT calledback0conf AND watching_id NOT NULL AND w.callback0conf NOT NULL AND w.watching')
|
||||
trace "[do_callbacks] callbacks0conf=${callbacks}"
|
||||
|
||||
local returncode
|
||||
@@ -30,7 +30,7 @@ do_callbacks() {
|
||||
fi
|
||||
done
|
||||
|
||||
callbacks=$(sql 'SELECT DISTINCT w.callback1conf, address, txid, vout, amount, confirmations, timereceived, fee, size, vsize, blockhash, blockheight, blocktime, w.id, is_replaceable, pub32_index, pub32, label, derivation_path, event_message FROM watching w, watching_tx wt, tx t LEFT JOIN watching_by_pub32 w32 ON watching_by_pub32_id = w32.id WHERE w.id = watching_id AND tx_id = t.id AND NOT calledback1conf AND confirmations>0 AND w.callback1conf NOT NULL AND w.watching')
|
||||
callbacks=$(sql 'SELECT DISTINCT w.callback1conf, address, txid, vout, amount, confirmations, timereceived, fee, size, vsize, blockhash, blockheight, blocktime, w.id, is_replaceable, pub32_index, pub32, label, derivation_path, event_message, hash FROM watching w, watching_tx wt, tx t LEFT JOIN watching_by_pub32 w32 ON watching_by_pub32_id = w32.id WHERE w.id = watching_id AND tx_id = t.id AND NOT calledback1conf AND confirmations>0 AND w.callback1conf NOT NULL AND w.watching')
|
||||
trace "[do_callbacks] callbacks1conf=${callbacks}"
|
||||
|
||||
for row in ${callbacks}
|
||||
@@ -45,7 +45,7 @@ do_callbacks() {
|
||||
done
|
||||
|
||||
callbacks=$(sql "SELECT id, label, bolt11, callback_url, payment_hash, msatoshi, status, pay_index, msatoshi_received, paid_at, description, expires_at FROM ln_invoice WHERE NOT calledback AND callback_failed")
|
||||
trace "[do_callbacks LN] ln_callbacks=${callbacks}"
|
||||
trace "[do_callbacks] ln_callbacks=${callbacks}"
|
||||
|
||||
for row in ${callbacks}
|
||||
do
|
||||
@@ -152,6 +152,7 @@ build_callback() {
|
||||
local derivation_path
|
||||
|
||||
local event_message
|
||||
local hash
|
||||
|
||||
# w.callback0conf, address, txid, vout, amount, confirmations, timereceived, fee, size, vsize, blockhash, blockheight, blocktime,
|
||||
# w.id, is_replaceable, pub32_index, pub32, label, derivation_path, event_message
|
||||
@@ -171,6 +172,8 @@ build_callback() {
|
||||
trace "[build_callback] address=${address}"
|
||||
txid=$(echo "${row}" | cut -d '|' -f3)
|
||||
trace "[build_callback] txid=${txid}"
|
||||
hash=$(echo "${row}" | cut -d '|' -f21)
|
||||
trace "[build_callback] hash=${hash}"
|
||||
vout_n=$(echo "${row}" | cut -d '|' -f4)
|
||||
trace "[build_callback] vout_n=${vout_n}"
|
||||
sent_amount=$(echo "${row}" | cut -d '|' -f5 | awk '{ printf "%.8f", $0 }')
|
||||
@@ -192,6 +195,7 @@ build_callback() {
|
||||
vsize=$(echo "${row}" | cut -d '|' -f10)
|
||||
trace "[build_callback] vsize=${vsize}"
|
||||
is_replaceable=$(echo "${row}" | cut -d '|' -f15)
|
||||
is_replaceable=$([ "${is_replaceable}" -eq "1" ] && echo "true" || echo "false")
|
||||
trace "[build_callback] is_replaceable=${is_replaceable}"
|
||||
blockhash=$(echo "${row}" | cut -d '|' -f11)
|
||||
trace "[build_callback] blockhash=${blockhash}"
|
||||
@@ -215,7 +219,8 @@ build_callback() {
|
||||
|
||||
data="{\"id\":\"${id}\","
|
||||
data="${data}\"address\":\"${address}\","
|
||||
data="${data}\"hash\":\"${txid}\","
|
||||
data="${data}\"txid\":\"${txid}\","
|
||||
data="${data}\"hash\":\"${hash}\","
|
||||
data="${data}\"vout_n\":${vout_n},"
|
||||
data="${data}\"sent_amount\":${sent_amount},"
|
||||
data="${data}\"confirmations\":${confirmations},"
|
||||
@@ -225,7 +230,7 @@ build_callback() {
|
||||
if [ -n "${fee}" ]; then
|
||||
data="${data}\"fees\":${fee},"
|
||||
fi
|
||||
data="${data}\"is_replaceable\":${is_replaceable},"
|
||||
data="${data}\"replaceable\":${is_replaceable},"
|
||||
if [ -n "${blocktime}" ]; then
|
||||
data="${data}\"blockhash\":\"${blockhash}\","
|
||||
data="${data}\"blocktime\":\"$(date -Is -d @${blocktime})\","
|
||||
|
||||
@@ -23,7 +23,7 @@ do_callbacks_txid() {
|
||||
build_callback_txid ${row}
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
if [ "${returncode}" -eq 0 ]; then
|
||||
if [ "${returncode}" -eq "0" ]; then
|
||||
id=$(echo "${row}" | cut -d '|' -f1)
|
||||
sql "UPDATE watching_by_txid SET calledback1conf=1 WHERE id=\"${id}\""
|
||||
trace_rc $?
|
||||
@@ -39,7 +39,8 @@ do_callbacks_txid() {
|
||||
do
|
||||
build_callback_txid ${row}
|
||||
returncode=$?
|
||||
if [ "${returncode}" -eq 0 ]; then
|
||||
trace_rc ${returncode}
|
||||
if [ "${returncode}" -eq "0" ]; then
|
||||
id=$(echo "${row}" | cut -d '|' -f1)
|
||||
sql "UPDATE watching_by_txid SET calledbackxconf=1, watching=0 WHERE id=\"${id}\""
|
||||
trace_rc $?
|
||||
@@ -136,6 +137,9 @@ build_callback_txid() {
|
||||
trace "[build_callback_txid] Number of confirmations for tx is not enough to call back."
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
trace "[build_callback_txid] Couldn't get tx from the Bitcoin node."
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ compute_vin_total_amount()
|
||||
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}")
|
||||
vin_raw_tx=$(get_rawtransaction "${vin_txid}" | tr -d '\n')
|
||||
returncode=$?
|
||||
if [ "${returncode}" -ne 0 ]; then
|
||||
return ${returncode}
|
||||
|
||||
@@ -66,8 +66,9 @@ confirmation() {
|
||||
|
||||
local tx=$(sql "SELECT id FROM tx WHERE txid=\"${txid}\"")
|
||||
local id_inserted
|
||||
local tx_raw_details=$(get_rawtransaction ${txid})
|
||||
local tx_raw_details=$(get_rawtransaction ${txid} | tr -d '\n')
|
||||
local tx_nb_conf=$(echo "${tx_details}" | jq -r '.result.confirmations // 0')
|
||||
local tx_hash=$(echo "${tx_raw_details}" | jq '.result.hash')
|
||||
|
||||
# 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
|
||||
@@ -80,14 +81,13 @@ confirmation() {
|
||||
|
||||
# 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 tx_replaceable=$(echo "${tx_details}" | jq -r '.result."bip125-replaceable"')
|
||||
tx_replaceable=$([ ${tx_replaceable} = "yes" ] && echo "true" || echo "false")
|
||||
|
||||
local fees=$(compute_fees "${txid}")
|
||||
trace "[confirmation] fees=${fees}"
|
||||
@@ -184,8 +184,8 @@ confirmation() {
|
||||
if [ -n "${event_message}" ]; then
|
||||
# There's an event message, let's publish it!
|
||||
|
||||
trace "[confirmation] mosquitto_pub -h broker -t tx_confirmation -m \"{\"txid\":\"${txid}\",\"address\":\"${address}\",\"vout_n\":${tx_vout_n},\"amount\":${tx_vout_amount},\"confirmations\":${tx_nb_conf},\"eventMessage\":\"${event_message}\"}\""
|
||||
response=$(mosquitto_pub -h broker -t tx_confirmation -m "{\"txid\":\"${txid}\",\"address\":\"${address}\",\"vout_n\":${tx_vout_n},\"amount\":${tx_vout_amount},\"confirmations\":${tx_nb_conf},\"eventMessage\":\"${event_message}\"}")
|
||||
trace "[confirmation] mosquitto_pub -h broker -t tx_confirmation -m \"{\"txid\":\"${txid}\",\"hash\":${tx_hash},\"address\":\"${address}\",\"vout_n\":${tx_vout_n},\"amount\":${tx_vout_amount},\"confirmations\":${tx_nb_conf},\"eventMessage\":\"${event_message}\"}\""
|
||||
response=$(mosquitto_pub -h broker -t tx_confirmation -m "{\"txid\":\"${txid}\",\"hash\":${tx_hash},\"address\":\"${address}\",\"vout_n\":${tx_vout_n},\"amount\":${tx_vout_amount},\"confirmations\":${tx_nb_conf},\"eventMessage\":\"${event_message}\"}")
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
fi
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
. ./trace.sh
|
||||
. ./callbacks_txid.sh
|
||||
. ./blockchainrpc.sh
|
||||
. ./batching.sh
|
||||
|
||||
newblock() {
|
||||
trace "Entering newblock()..."
|
||||
@@ -22,4 +23,5 @@ newblock() {
|
||||
trace_rc ${returncode}
|
||||
|
||||
do_callbacks_txid
|
||||
batch_check_webhooks
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
. ./call_lightningd.sh
|
||||
. ./ots.sh
|
||||
. ./newblock.sh
|
||||
. ./batching.sh
|
||||
|
||||
main() {
|
||||
trace "Entering main()..."
|
||||
@@ -99,8 +100,35 @@ main() {
|
||||
;;
|
||||
unwatch)
|
||||
# curl (GET) 192.168.111.152:8080/unwatch/2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp
|
||||
# or
|
||||
# POST http://192.168.111.152:8080/unwatch
|
||||
# BODY {"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","unconfirmedCallbackURL":"192.168.111.233:1111/callback0conf","confirmedCallbackURL":"192.168.111.233:1111/callback1conf"}
|
||||
# or
|
||||
# BODY {"id":3124}
|
||||
|
||||
response=$(unwatchrequest "${line}")
|
||||
# args:
|
||||
# - address: string, required
|
||||
# - unconfirmedCallbackURL: string, optional
|
||||
# - confirmedCallbackURL: string, optional
|
||||
# or
|
||||
# - id: the id returned by the watch
|
||||
|
||||
local address="null"
|
||||
local unconfirmedCallbackURL="null"
|
||||
local confirmedCallbackURL="null"
|
||||
local watchid="null"
|
||||
|
||||
# Let's make it work even for a GET request (equivalent to a POST with empty json object body)
|
||||
if [ "$http_method" = "POST" ]; then
|
||||
address=$(echo "${line}" | jq -r ".address")
|
||||
unconfirmedCallbackURL=$(echo "${line}" | jq ".unconfirmedCallbackURL")
|
||||
confirmedCallbackURL=$(echo "${line}" | jq ".confirmedCallbackURL")
|
||||
watchid=$(echo "${line}" | jq ".id")
|
||||
else
|
||||
address=$(echo "${line}" | cut -d ' ' -f2 | cut -d '/' -f3)
|
||||
fi
|
||||
|
||||
response=$(unwatchrequest "${watchid}" "${address}" "${unconfirmedCallbackURL}" "${confirmedCallbackURL}")
|
||||
response_to_client "${response}" ${?}
|
||||
break
|
||||
;;
|
||||
@@ -157,6 +185,28 @@ main() {
|
||||
response_to_client "${response}" ${?}
|
||||
break
|
||||
;;
|
||||
unwatchtxid)
|
||||
# POST http://192.168.111.152:8080/unwatchtxid
|
||||
# BODY {"txid":"b081ca7724386f549cf0c16f71db6affeb52ff7a0d9b606fb2e5c43faffd3387","unconfirmedCallbackURL":"192.168.111.233:1111/callback0conf","confirmedCallbackURL":"192.168.111.233:1111/callback1conf"}
|
||||
# or
|
||||
# BODY {"id":3124}
|
||||
|
||||
# args:
|
||||
# - txid: string, required
|
||||
# - unconfirmedCallbackURL: string, optional
|
||||
# - confirmedCallbackURL: string, optional
|
||||
# or
|
||||
# - id: the id returned by watchtxid
|
||||
|
||||
local txid=$(echo "${line}" | jq -r ".txid")
|
||||
local unconfirmedCallbackURL=$(echo "${line}" | jq ".unconfirmedCallbackURL")
|
||||
local confirmedCallbackURL=$(echo "${line}" | jq ".confirmedCallbackURL")
|
||||
local watchid=$(echo "${line}" | jq ".id")
|
||||
|
||||
response=$(unwatchtxidrequest "${watchid}" "${txid}" "${unconfirmedCallbackURL}" "${confirmedCallbackURL}")
|
||||
response_to_client "${response}" ${?}
|
||||
break
|
||||
;;
|
||||
getactivewatches)
|
||||
# curl (GET) 192.168.111.152:8080/getactivewatches
|
||||
|
||||
@@ -300,21 +350,229 @@ main() {
|
||||
response_to_client "${response}" ${?}
|
||||
break
|
||||
;;
|
||||
createbatcher)
|
||||
# POST http://192.168.111.152:8080/createbatcher
|
||||
#
|
||||
# args:
|
||||
# - batcherLabel, optional, id can be used to reference the batcher
|
||||
# - confTarget, optional, overriden by batchspend's confTarget, default Bitcoin Core conf_target will be used if not supplied
|
||||
# NOTYET - feeRate, sat/vB, optional, overrides confTarget if supplied, overriden by batchspend's feeRate, default Bitcoin Core fee policy will be used if not supplied
|
||||
#
|
||||
# response:
|
||||
# - batcherId, the batcher id
|
||||
#
|
||||
# BODY {"batcherLabel":"lowfees","confTarget":32}
|
||||
# NOTYET BODY {"batcherLabel":"highfees","feeRate":231.8}
|
||||
|
||||
response=$(createbatcher "${line}")
|
||||
response_to_client "${response}" ${?}
|
||||
break
|
||||
;;
|
||||
updatebatcher)
|
||||
# POST http://192.168.111.152:8080/updatebatcher
|
||||
#
|
||||
# args:
|
||||
# - batcherId, optional, batcher id to update, will update default batcher if not supplied
|
||||
# - batcherLabel, optional, id can be used to reference the batcher, will update default batcher if not supplied, if id is present then change the label with supplied text
|
||||
# - confTarget, optional, new confirmation target for the batcher
|
||||
# NOTYET - feeRate, sat/vB, optional, new feerate for the batcher
|
||||
#
|
||||
# response:
|
||||
# - batcherId, the batcher id
|
||||
# - batcherLabel, the batcher label
|
||||
# - confTarget, the batcher default confirmation target
|
||||
# NOTYET - feeRate, the batcher default feerate
|
||||
#
|
||||
# BODY {"batcherId":5,"confTarget":12}
|
||||
# NOTYET BODY {"batcherLabel":"highfees","feeRate":400}
|
||||
# NOTYET BODY {"batcherId":3,"label":"ultrahighfees","feeRate":800}
|
||||
# BODY {"batcherLabel":"fast","confTarget":2}
|
||||
|
||||
response=$(updatebatcher "${line}")
|
||||
response_to_client "${response}" ${?}
|
||||
break
|
||||
;;
|
||||
addtobatch)
|
||||
# POST http://192.168.111.152:8080/addtobatch
|
||||
#
|
||||
# args:
|
||||
# - address, required, desination address
|
||||
# - amount, required, amount to send to the destination address
|
||||
# - outputLabel, optional, if you want to reference this output
|
||||
# - batcherId, optional, the id of the batcher to which the output will be added, default batcher if not supplied, overrides batcherLabel
|
||||
# - batcherLabel, optional, the label of the batcher to which the output will be added, default batcher if not supplied
|
||||
# - webhookUrl, optional, the webhook to call when the batch is broadcast
|
||||
#
|
||||
# response:
|
||||
# - batcherId, the id of the batcher
|
||||
# - outputId, the id of the added output
|
||||
# - nbOutputs, the number of outputs currently in the batch
|
||||
# - oldest, the timestamp of the oldest output in the batch
|
||||
# - total, the current sum of the batch's output amounts
|
||||
#
|
||||
# BODY {"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","amount":0.00233}
|
||||
# BODY {"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","amount":0.00233,"batcherId":34,"webhookUrl":"https://myCypherApp:3000/batchExecuted"}
|
||||
# BODY {"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","amount":0.00233,"batcherLabel":"lowfees","webhookUrl":"https://myCypherApp:3000/batchExecuted"}
|
||||
# BODY {"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp","amount":0.00233,"batcherId":34,"webhookUrl":"https://myCypherApp:3000/batchExecuted"}
|
||||
|
||||
response=$(addtobatching $(echo "${line}" | jq -r ".address") $(echo "${line}" | jq ".amount"))
|
||||
response=$(addtobatch "${line}")
|
||||
response_to_client "${response}" ${?}
|
||||
break
|
||||
;;
|
||||
removefrombatch)
|
||||
# POST http://192.168.111.152:8080/removefrombatch
|
||||
#
|
||||
# args:
|
||||
# - outputId, required, id of the output to remove
|
||||
#
|
||||
# response:
|
||||
# - batcherId, the id of the batcher
|
||||
# - outputId, the id of the removed output if found
|
||||
# - nbOutputs, the number of outputs currently in the batch
|
||||
# - oldest, the timestamp of the oldest output in the batch
|
||||
# - total, the current sum of the batch's output amounts
|
||||
#
|
||||
# BODY {"outputId":72}
|
||||
|
||||
response=$(removefrombatch "${line}")
|
||||
response_to_client "${response}" ${?}
|
||||
break
|
||||
;;
|
||||
batchspend)
|
||||
# GET http://192.168.111.152:8080/batchspend
|
||||
# POST http://192.168.111.152:8080/batchspend
|
||||
#
|
||||
# args:
|
||||
# - batcherId, optional, id of the batcher to execute, overrides batcherLabel, default batcher will be spent if not supplied
|
||||
# - batcherLabel, optional, label of the batcher to execute, default batcher will be executed if not supplied
|
||||
# - confTarget, optional, overrides default value of createbatcher, default to value of createbatcher, default Bitcoin Core conf_target will be used if not supplied
|
||||
# NOTYET - feeRate, optional, overrides confTarget if supplied, overrides default value of createbatcher, default to value of createbatcher, default Bitcoin Core value will be used if not supplied
|
||||
#
|
||||
# response:
|
||||
# - batcherId, id of the executed batcher
|
||||
# - confTarget, conf_target used for the spend
|
||||
# - nbOutputs, the number of outputs spent in the batch
|
||||
# - oldest, the timestamp of the oldest output in the spent batch
|
||||
# - total, the sum of the spent batch's output amounts
|
||||
# - txid, the batch transaction id
|
||||
# - hash, the transaction hash
|
||||
# - tx details: firstseen, size, vsize, replaceable, fee
|
||||
# - outputs
|
||||
#
|
||||
# {"result":{
|
||||
# "batcherId":34,
|
||||
# "confTarget":6,
|
||||
# "nbOutputs":83,
|
||||
# "oldest":123123,
|
||||
# "total":10.86990143,
|
||||
# "txid":"af867c86000da76df7ddb1054b273ca9e034e8c89d049b5b2795f9f590f67648",
|
||||
# "hash":"af867c86000da76df7ddb1054b273ca9e034e8c89d049b5b2795f9f590f67648",
|
||||
# "details":{
|
||||
# "firstseen":123123,
|
||||
# "size":424,
|
||||
# "vsize":371,
|
||||
# "replaceable":yes,
|
||||
# "fee":0.00004112
|
||||
# },
|
||||
# "outputs":{
|
||||
# "1abc":0.12,
|
||||
# "3abc":0.66,
|
||||
# "bc1abc":2.848,
|
||||
# ...
|
||||
# }
|
||||
# }
|
||||
# },"error":null}
|
||||
#
|
||||
# BODY {}
|
||||
# BODY {"batcherId":34,"confTarget":12}
|
||||
# NOTYET BODY {"batcherLabel":"highfees","feeRate":233.7}
|
||||
# BODY {"batcherId":411,"confTarget":6}
|
||||
|
||||
response=$(batchspend "${line}")
|
||||
response_to_client "${response}" ${?}
|
||||
break
|
||||
;;
|
||||
getbatcher)
|
||||
# POST (GET) http://192.168.111.152:8080/getbatcher
|
||||
#
|
||||
# args:
|
||||
# - batcherId, optional, id of the batcher, overrides batcherLabel, default batcher will be used if not supplied
|
||||
# - batcherLabel, optional, label of the batcher, default batcher will be used if not supplied
|
||||
#
|
||||
# response:
|
||||
# {"result":{"batcherId":1,"batcherLabel":"default","confTarget":6,"nbOutputs":12,"oldest":123123,"total":0.86990143},"error":null}
|
||||
#
|
||||
# BODY {}
|
||||
# BODY {"batcherId":34}
|
||||
|
||||
response=$(getbatcher "${line}")
|
||||
response_to_client "${response}" ${?}
|
||||
break
|
||||
;;
|
||||
getbatchdetails)
|
||||
# POST (GET) http://192.168.111.152:8080/getbatchdetails
|
||||
#
|
||||
# args:
|
||||
# - batcherId, optional, id of the batcher, overrides batcherLabel, default batcher will be spent if not supplied
|
||||
# - batcherLabel, optional, label of the batcher, default batcher will be used if not supplied
|
||||
# - txid, optional, if you want the details of an executed batch, supply the batch txid, will return current pending batch
|
||||
# if not supplied
|
||||
#
|
||||
# response:
|
||||
# {"result":{
|
||||
# "batcherId":34,
|
||||
# "batcherLabel":"Special batcher for a special client",
|
||||
# "confTarget":6,
|
||||
# "nbOutputs":83,
|
||||
# "oldest":123123,
|
||||
# "total":10.86990143,
|
||||
# "txid":"af867c86000da76df7ddb1054b273ca9e034e8c89d049b5b2795f9f590f67648",
|
||||
# "hash":"af867c86000da76df7ddb1054b273ca9e034e8c89d049b5b2795f9f590f67648",
|
||||
# "details":{
|
||||
# "firstseen":123123,
|
||||
# "size":424,
|
||||
# "vsize":371,
|
||||
# "replaceable":yes,
|
||||
# "fee":0.00004112
|
||||
# },
|
||||
# "outputs":[
|
||||
# "1abc":0.12,
|
||||
# "3abc":0.66,
|
||||
# "bc1abc":2.848,
|
||||
# ...
|
||||
# ]
|
||||
# }
|
||||
# },"error":null}
|
||||
#
|
||||
# BODY {}
|
||||
# BODY {"batcherId":34}
|
||||
|
||||
response=$(getbatchdetails "${line}")
|
||||
response_to_client "${response}" ${?}
|
||||
break
|
||||
;;
|
||||
listbatchers)
|
||||
# curl (GET) http://192.168.111.152:8080/listbatchers
|
||||
#
|
||||
# response:
|
||||
# {"result":[
|
||||
# {"batcherId":1,"batcherLabel":"default","confTarget":6,"nbOutputs":12,"oldest":123123,"total":0.86990143},
|
||||
# {"batcherId":2,"batcherLabel":"lowfee","confTarget":32,"nbOutputs":44,"oldest":123123,"total":0.49827387},
|
||||
# {"batcherId":3,"batcherLabel":"highfee","confTarget":2,"nbOutputs":7,"oldest":123123,"total":4.16843782}
|
||||
# ],
|
||||
# "error":null}
|
||||
|
||||
response=$(listbatchers)
|
||||
response_to_client "${response}" ${?}
|
||||
break
|
||||
;;
|
||||
bitcoin_estimatesmartfee)
|
||||
# POST http://192.168.111.152:8080/bitcoin_estimatesmartfee
|
||||
# BODY {"confTarget":2}
|
||||
|
||||
response=$(bitcoin_estimatesmartfee $(echo "${line}" | jq -r ".confTarget"))
|
||||
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
|
||||
|
||||
326
proxy_docker/app/script/test-batching.sh
Executable file
326
proxy_docker/app/script/test-batching.sh
Executable file
@@ -0,0 +1,326 @@
|
||||
#!/bin/sh
|
||||
|
||||
# curl localhost:8888/listbatchers | jq
|
||||
# curl -d '{}' localhost:8888/getbatcher | jq
|
||||
# curl -d '{}' localhost:8888/getbatchdetails | jq
|
||||
# curl -d '{"outputLabel":"test002","address":"1abd","amount":0.0002}' localhost:8888/addtobatch | jq
|
||||
# curl -d '{}' localhost:8888/batchspend | jq
|
||||
# curl -d '{"outputId":1}' localhost:8888/removefrombatch | jq
|
||||
|
||||
# curl -d '{"batcherLabel":"lowfees","confTarget":32}' localhost:8888/createbatcher | jq
|
||||
# curl localhost:8888/listbatchers | jq
|
||||
|
||||
# curl -d '{"batcherLabel":"lowfees"}' localhost:8888/getbatcher | jq
|
||||
# curl -d '{"batcherLabel":"lowfees"}' localhost:8888/getbatchdetails | jq
|
||||
# curl -d '{"batcherLabel":"lowfees","outputLabel":"test002","address":"1abd","amount":0.0002}' localhost:8888/addtobatch | jq
|
||||
# curl -d '{"batcherLabel":"lowfees"}' localhost:8888/batchspend | jq
|
||||
# curl -d '{"batcherLabel":"lowfees","outputId":9}' localhost:8888/removefrombatch | jq
|
||||
|
||||
testbatching() {
|
||||
local response
|
||||
local id
|
||||
local id2
|
||||
local data
|
||||
local data2
|
||||
local address1
|
||||
local address2
|
||||
local amount1
|
||||
local amount2
|
||||
|
||||
local url1="$(hostname):1111/callback"
|
||||
echo "url1=${url1}"
|
||||
local url2="$(hostname):1112/callback"
|
||||
echo "url2=${url2}"
|
||||
|
||||
# List batchers (should show at least empty default batcher)
|
||||
echo "Testing listbatchers..."
|
||||
response=$(curl -s proxy:8888/listbatchers)
|
||||
echo "response=${response}"
|
||||
id=$(echo "${response}" | jq ".result[0].batcherId")
|
||||
echo "batcherId=${id}"
|
||||
if [ "${id}" -ne "1" ]; then
|
||||
exit 10
|
||||
fi
|
||||
echo "Tested listbatchers."
|
||||
|
||||
# getbatcher the default batcher
|
||||
echo "Testing getbatcher..."
|
||||
response=$(curl -sd '{}' localhost:8888/getbatcher)
|
||||
echo "response=${response}"
|
||||
data=$(echo "${response}" | jq -r ".result.batcherLabel")
|
||||
echo "batcherLabel=${data}"
|
||||
if [ "${data}" != "default" ]; then
|
||||
exit 20
|
||||
fi
|
||||
|
||||
response=$(curl -sd '{"batcherId":1}' localhost:8888/getbatcher)
|
||||
echo "response=${response}"
|
||||
data=$(echo "${response}" | jq -r ".result.batcherLabel")
|
||||
echo "batcherLabel=${data}"
|
||||
if [ "${data}" != "default" ]; then
|
||||
exit 25
|
||||
fi
|
||||
echo "Tested getbatcher."
|
||||
|
||||
# getbatchdetails the default batcher
|
||||
echo "Testing getbatchdetails..."
|
||||
response=$(curl -sd '{}' localhost:8888/getbatchdetails)
|
||||
echo "response=${response}"
|
||||
data=$(echo "${response}" | jq -r ".result.batcherLabel")
|
||||
echo "batcherLabel=${data}"
|
||||
if [ "${data}" != "default" ]; then
|
||||
exit 30
|
||||
fi
|
||||
echo "${response}" | jq -e ".result.outputs"
|
||||
if [ "$?" -ne 0 ]; then
|
||||
exit 32
|
||||
fi
|
||||
|
||||
response=$(curl -sd '{"batcherId":1}' localhost:8888/getbatchdetails)
|
||||
echo "response=${response}"
|
||||
data=$(echo "${response}" | jq -r ".result.batcherLabel")
|
||||
echo "batcherLabel=${data}"
|
||||
if [ "${data}" != "default" ]; then
|
||||
exit 35
|
||||
fi
|
||||
echo "${response}" | jq -e ".result.outputs"
|
||||
if [ "$?" -ne 0 ]; then
|
||||
exit 37
|
||||
fi
|
||||
echo "Tested getbatchdetails."
|
||||
|
||||
# addtobatch to default batcher
|
||||
echo "Testing addtobatch..."
|
||||
address1=$(curl -s localhost:8888/getnewaddress | jq -r ".address")
|
||||
echo "address1=${address1}"
|
||||
response=$(curl -sd '{"outputLabel":"test001","address":"'${address1}'","amount":0.001}' localhost:8888/addtobatch)
|
||||
echo "response=${response}"
|
||||
id=$(echo "${response}" | jq ".result.batcherId")
|
||||
echo "batcherId=${id}"
|
||||
if [ "${id}" -ne "1" ]; then
|
||||
exit 40
|
||||
fi
|
||||
id=$(echo "${response}" | jq -e ".result.outputId")
|
||||
if [ "$?" -ne 0 ]; then
|
||||
exit 42
|
||||
fi
|
||||
echo "outputId=${id}"
|
||||
|
||||
address2=$(curl -s localhost:8888/getnewaddress | jq -r ".address")
|
||||
echo "address2=${address2}"
|
||||
response=$(curl -sd '{"batcherId":1,"outputLabel":"test002","address":"'${address2}'","amount":22000000}' localhost:8888/addtobatch)
|
||||
echo "response=${response}"
|
||||
id2=$(echo "${response}" | jq ".result.batcherId")
|
||||
echo "batcherId=${id2}"
|
||||
if [ "${id2}" -ne "1" ]; then
|
||||
exit 47
|
||||
fi
|
||||
id2=$(echo "${response}" | jq -e ".result.outputId")
|
||||
if [ "$?" -ne 0 ]; then
|
||||
exit 50
|
||||
fi
|
||||
echo "outputId=${id2}"
|
||||
echo "Tested addtobatch."
|
||||
|
||||
# batchspend default batcher
|
||||
echo "Testing batchspend..."
|
||||
response=$(curl -sd '{}' localhost:8888/batchspend)
|
||||
echo "response=${response}"
|
||||
echo "${response}" | jq -e ".error"
|
||||
if [ "$?" -ne 0 ]; then
|
||||
exit 55
|
||||
fi
|
||||
echo "Tested batchspend."
|
||||
|
||||
# getbatchdetails the default batcher
|
||||
echo "Testing getbatchdetails..."
|
||||
response=$(curl -sd '{}' localhost:8888/getbatchdetails)
|
||||
echo "response=${response}"
|
||||
data=$(echo "${response}" | jq ".result.nbOutputs")
|
||||
echo "nbOutputs=${data}"
|
||||
echo "Tested getbatchdetails."
|
||||
|
||||
# removefrombatch from default batcher
|
||||
echo "Testing removefrombatch..."
|
||||
response=$(curl -sd '{"outputId":'${id}'}' localhost:8888/removefrombatch)
|
||||
echo "response=${response}"
|
||||
id=$(echo "${response}" | jq ".result.batcherId")
|
||||
echo "batcherId=${id}"
|
||||
if [ "${id}" -ne "1" ]; then
|
||||
exit 60
|
||||
fi
|
||||
|
||||
response=$(curl -sd '{"outputId":'${id2}'}' localhost:8888/removefrombatch)
|
||||
echo "response=${response}"
|
||||
id=$(echo "${response}" | jq ".result.batcherId")
|
||||
echo "batcherId=${id}"
|
||||
if [ "${id}" -ne "1" ]; then
|
||||
exit 64
|
||||
fi
|
||||
echo "Tested removefrombatch."
|
||||
|
||||
# getbatchdetails the default batcher
|
||||
echo "Testing getbatchdetails..."
|
||||
response=$(curl -sd '{"batcherId":1}' localhost:8888/getbatchdetails)
|
||||
echo "response=${response}"
|
||||
data2=$(echo "${response}" | jq ".result.nbOutputs")
|
||||
echo "nbOutputs=${data2}"
|
||||
if [ "${data2}" -ne "$((${data}-2))" ]; then
|
||||
exit 68
|
||||
fi
|
||||
echo "Tested getbatchdetails."
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Create a batcher
|
||||
echo "Testing createbatcher..."
|
||||
response=$(curl -s -H 'Content-Type: application/json' -d '{"batcherLabel":"testbatcher","confTarget":32}' proxy:8888/createbatcher)
|
||||
echo "response=${response}"
|
||||
id=$(echo "${response}" | jq -e ".result.batcherId")
|
||||
if [ "$?" -ne "0" ]; then
|
||||
exit 70
|
||||
fi
|
||||
|
||||
# List batchers (should show at least default and testbatcher batchers)
|
||||
echo "Testing listbatches..."
|
||||
response=$(curl -s proxy:8888/listbatchers)
|
||||
echo "response=${response}"
|
||||
id=$(echo "${response}" | jq '.result[] | select(.batcherLabel == "testbatcher") | .batcherId')
|
||||
echo "batcherId=${id}"
|
||||
if [ -z "${id}" ]; then
|
||||
exit 75
|
||||
fi
|
||||
echo "Tested listbatchers."
|
||||
|
||||
# getbatcher the testbatcher batcher
|
||||
echo "Testing getbatcher..."
|
||||
response=$(curl -sd '{"batcherId":'${id}'}' localhost:8888/getbatcher)
|
||||
echo "response=${response}"
|
||||
data=$(echo "${response}" | jq -r ".result.batcherLabel")
|
||||
echo "batcherLabel=${data}"
|
||||
if [ "${data}" != "testbatcher" ]; then
|
||||
exit 80
|
||||
fi
|
||||
|
||||
response=$(curl -sd '{"batcherLabel":"testbatcher"}' localhost:8888/getbatcher)
|
||||
echo "response=${response}"
|
||||
data=$(echo "${response}" | jq -r ".result.batcherId")
|
||||
echo "batcherId=${data}"
|
||||
if [ "${data}" != "${id}" ]; then
|
||||
exit 90
|
||||
fi
|
||||
echo "Tested getbatcher."
|
||||
|
||||
# getbatchdetails the testbatcher batcher
|
||||
echo "Testing getbatchdetails..."
|
||||
response=$(curl -sd '{"batcherLabel":"testbatcher"}' localhost:8888/getbatchdetails)
|
||||
echo "response=${response}"
|
||||
data=$(echo "${response}" | jq -r ".result.batcherId")
|
||||
echo "batcherId=${data}"
|
||||
if [ "${data}" != "${id}" ]; then
|
||||
exit 100
|
||||
fi
|
||||
echo "${response}" | jq -e ".result.outputs"
|
||||
if [ "$?" -ne 0 ]; then
|
||||
exit 110
|
||||
fi
|
||||
|
||||
response=$(curl -sd '{"batcherId":'${id}'}' localhost:8888/getbatchdetails)
|
||||
echo "response=${response}"
|
||||
data=$(echo "${response}" | jq -r ".result.batcherLabel")
|
||||
echo "batcherLabel=${data}"
|
||||
if [ "${data}" != "testbatcher" ]; then
|
||||
exit 120
|
||||
fi
|
||||
echo "${response}" | jq -e ".result.outputs"
|
||||
if [ "$?" -ne 0 ]; then
|
||||
exit 130
|
||||
fi
|
||||
echo "Tested getbatchdetails."
|
||||
|
||||
# addtobatch to testbatcher batcher
|
||||
echo "Testing addtobatch..."
|
||||
address1=$(curl -s localhost:8888/getnewaddress | jq -r ".address")
|
||||
echo "address1=${address1}"
|
||||
response=$(curl -sd '{"batcherId":'${id}',"outputLabel":"test001","address":"'${address1}'","amount":0.001,"webhookUrl":"'${url1}'/'${address1}'"}' localhost:8888/addtobatch)
|
||||
echo "response=${response}"
|
||||
data=$(echo "${response}" | jq ".result.batcherId")
|
||||
echo "batcherId=${data}"
|
||||
if [ "${data}" -ne "${id}" ]; then
|
||||
exit 140
|
||||
fi
|
||||
id2=$(echo "${response}" | jq -e ".result.outputId")
|
||||
if [ "$?" -ne 0 ]; then
|
||||
exit 142
|
||||
fi
|
||||
echo "outputId=${id2}"
|
||||
|
||||
address2=$(curl -s localhost:8888/getnewaddress | jq -r ".address")
|
||||
echo "address2=${address2}"
|
||||
response=$(curl -sd '{"batcherLabel":"testbatcher","outputLabel":"test002","address":"'${address2}'","amount":0.002,"webhookUrl":"'${url2}'/'${address2}'"}' localhost:8888/addtobatch)
|
||||
echo "response=${response}"
|
||||
data=$(echo "${response}" | jq ".result.batcherId")
|
||||
echo "batcherId=${data}"
|
||||
if [ "${data}" -ne "${id}" ]; then
|
||||
exit 150
|
||||
fi
|
||||
id2=$(echo "${response}" | jq -e ".result.outputId")
|
||||
if [ "$?" -ne 0 ]; then
|
||||
exit 152
|
||||
fi
|
||||
echo "outputId=${id2}"
|
||||
echo "Tested addtobatch."
|
||||
|
||||
# batchspend testbatcher batcher
|
||||
echo "Testing batchspend..."
|
||||
response=$(curl -sd '{"batcherLabel":"testbatcher"}' localhost:8888/batchspend)
|
||||
echo "response=${response}"
|
||||
data2=$(echo "${response}" | jq -e ".result.txid")
|
||||
if [ "$?" -ne 0 ]; then
|
||||
exit 160
|
||||
fi
|
||||
echo "txid=${data2}"
|
||||
data=$(echo "${response}" | jq ".result.outputs | length")
|
||||
if [ "${data}" -ne "2" ]; then
|
||||
exit 162
|
||||
fi
|
||||
echo "Tested batchspend."
|
||||
|
||||
# getbatchdetails the testbatcher batcher
|
||||
echo "Testing getbatchdetails..."
|
||||
echo "txid=${data2}"
|
||||
response=$(curl -sd '{"batcherLabel":"testbatcher","txid":'${data2}'}' localhost:8888/getbatchdetails)
|
||||
echo "response=${response}"
|
||||
data=$(echo "${response}" | jq ".result.nbOutputs")
|
||||
echo "nbOutputs=${data}"
|
||||
if [ "${data}" -ne "2" ]; then
|
||||
exit 170
|
||||
fi
|
||||
echo "Tested getbatchdetails."
|
||||
|
||||
# List batchers
|
||||
# Add to batch
|
||||
# List batchers
|
||||
# Remove from batch
|
||||
# List batchers
|
||||
}
|
||||
|
||||
wait_for_callbacks() {
|
||||
nc -vlp1111 -e sh -c 'echo -en "HTTP/1.1 200 OK\r\n\r\n" ; timeout 1 tee /dev/tty | cat ; echo 1>&2' &
|
||||
nc -vlp1112 -e sh -c 'echo -en "HTTP/1.1 200 OK\r\n\r\n" ; timeout 1 tee /dev/tty | cat ; echo 1>&2' &
|
||||
}
|
||||
|
||||
wait_for_callbacks
|
||||
testbatching
|
||||
wait
|
||||
@@ -6,16 +6,27 @@
|
||||
unwatchrequest() {
|
||||
trace "Entering unwatchrequest()..."
|
||||
|
||||
local request=${1}
|
||||
local address=$(echo "${request}" | cut -d ' ' -f2 | cut -d '/' -f3)
|
||||
local watchid=${1}
|
||||
local address=${2}
|
||||
local unconfirmedCallbackURL=${3}
|
||||
local confirmedCallbackURL=${4}
|
||||
local returncode
|
||||
trace "[unwatchrequest] Unwatch request on address ${address}"
|
||||
trace "[unwatchrequest] Unwatch request id ${watchid} on address ${address} with url0conf ${unconfirmedCallbackURL} and url1conf ${confirmedCallbackURL}"
|
||||
|
||||
sql "UPDATE watching SET watching=0 WHERE address=\"${address}\""
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
if [ "${watchid}" != "null" ]; then
|
||||
sql "UPDATE watching SET watching=0 WHERE id=${watchid}"
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
|
||||
data="{\"event\":\"unwatch\",\"id\":${watchid}}"
|
||||
else
|
||||
sql "UPDATE watching SET watching=0 WHERE address='${address}' AND callback0conf=${unconfirmedCallbackURL} AND callback1conf=${confirmedCallbackURL}"
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
|
||||
data="{\"event\":\"unwatch\",\"address\":\"${address}\",\"unconfirmedCallbackURL\":${unconfirmedCallbackURL},\"confirmedCallbackURL\":${confirmedCallbackURL}}"
|
||||
fi
|
||||
|
||||
data="{\"event\":\"unwatch\",\"address\":\"${address}\"}"
|
||||
trace "[unwatchrequest] responding=${data}"
|
||||
|
||||
echo "${data}"
|
||||
@@ -80,3 +91,34 @@ unwatchpub32labelrequest() {
|
||||
|
||||
return ${returncode}
|
||||
}
|
||||
|
||||
unwatchtxidrequest() {
|
||||
trace "Entering unwatchtxidrequest()..."
|
||||
|
||||
local watchid=${1}
|
||||
local txid=${2}
|
||||
local unconfirmedCallbackURL=${3}
|
||||
local confirmedCallbackURL=${4}
|
||||
local returncode
|
||||
trace "[unwatchtxidrequest] Unwatch request id ${watchid} on txid ${txid} with url0conf ${unconfirmedCallbackURL} and url1conf ${confirmedCallbackURL}"
|
||||
|
||||
if [ "${watchid}" != "null" ]; then
|
||||
sql "UPDATE watching_by_txid SET watching=0 WHERE id=${watchid}"
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
|
||||
data="{\"event\":\"unwatchtxid\",\"id\":${watchid}}"
|
||||
else
|
||||
sql "UPDATE watching_by_txid SET watching=0 WHERE txid='${txid}' AND callback0conf=${unconfirmedCallbackURL} AND callback1conf=${confirmedCallbackURL}"
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
|
||||
data="{\"event\":\"unwatchtxid\",\"txid\":\"${txid}\",\"unconfirmedCallbackURL\":${unconfirmedCallbackURL},\"confirmedCallbackURL\":${confirmedCallbackURL}}"
|
||||
fi
|
||||
|
||||
trace "[unwatchtxidrequest] responding=${data}"
|
||||
|
||||
echo "${data}"
|
||||
|
||||
return ${returncode}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ spend() {
|
||||
|
||||
# Let's get transaction details on the spending wallet so that we have fee information
|
||||
tx_details=$(get_transaction ${txid} "spender")
|
||||
tx_raw_details=$(get_rawtransaction ${txid})
|
||||
tx_raw_details=$(get_rawtransaction ${txid} | tr -d '\n')
|
||||
|
||||
# Amounts and fees are negative when spending so we absolute those fields
|
||||
local tx_hash=$(echo "${tx_raw_details}" | jq '.result.hash')
|
||||
@@ -43,8 +43,8 @@ spend() {
|
||||
local tx_amount=$(echo "${tx_details}" | jq '.result.amount | fabs' | awk '{ printf "%.8f", $0 }')
|
||||
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 tx_replaceable=$(echo "${tx_details}" | jq -r '.result."bip125-replaceable"')
|
||||
tx_replaceable=$([ ${tx_replaceable} = "yes" ] && echo "true" || echo "false")
|
||||
local fees=$(echo "${tx_details}" | jq '.result.fee | fabs' | awk '{ printf "%.8f", $0 }')
|
||||
# Sometimes raw tx are too long to be passed as paramater, so let's write
|
||||
# it to a temp file for it to be read by sqlite3 and then delete the file
|
||||
@@ -69,7 +69,7 @@ spend() {
|
||||
########################################################################################################
|
||||
|
||||
# Let's insert the txid in our little DB -- then we'll already have it when receiving confirmation
|
||||
sql "INSERT OR IGNORE INTO tx (txid, hash, confirmations, timereceived, fee, size, vsize, is_replaceable, raw_tx) VALUES (\"${txid}\", ${tx_hash}, 0, ${tx_ts_firstseen}, ${fees}, ${tx_size}, ${tx_vsize}, ${tx_replaceable}, readfile('spend-rawtx-${txid}-$$.blob'))"
|
||||
sql "INSERT OR IGNORE INTO tx (txid, hash, confirmations, timereceived, fee, size, vsize, is_replaceable, conf_target, raw_tx) VALUES (\"${txid}\", ${tx_hash}, 0, ${tx_ts_firstseen}, ${fees}, ${tx_size}, ${tx_vsize}, ${tx_replaceable}, ${conf_target}, readfile('spend-rawtx-${txid}-$$.blob'))"
|
||||
trace_rc $?
|
||||
id_inserted=$(sql "SELECT id FROM tx WHERE txid=\"${txid}\"")
|
||||
trace_rc $?
|
||||
@@ -77,7 +77,7 @@ spend() {
|
||||
trace_rc $?
|
||||
|
||||
data="{\"status\":\"accepted\""
|
||||
data="${data},\"hash\":\"${txid}\",\"details\":{\"address\":\"${address}\",\"amount\":${amount},\"firstseen\":${tx_ts_firstseen},\"size\":${tx_size},\"vsize\":${tx_vsize},\"replaceable\":${replaceable},\"fee\":${fees},\"subtractfeefromamount\":${subtractfeefromamount}}}"
|
||||
data="${data},\"txid\":\"${txid}\",\"hash\":${tx_hash},\"details\":{\"address\":\"${address}\",\"amount\":${amount},\"firstseen\":${tx_ts_firstseen},\"size\":${tx_size},\"vsize\":${tx_vsize},\"replaceable\":${tx_replaceable},\"fee\":${fees},\"subtractfeefromamount\":${subtractfeefromamount}}}"
|
||||
|
||||
# Delete the temp file containing the raw tx (see above)
|
||||
rm spend-rawtx-${txid}-$$.blob
|
||||
@@ -156,6 +156,7 @@ get_txns_spending() {
|
||||
|
||||
return ${returncode}
|
||||
}
|
||||
|
||||
getbalance() {
|
||||
trace "Entering getbalance()..."
|
||||
|
||||
@@ -293,113 +294,6 @@ getnewaddress() {
|
||||
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
|
||||
local tx_details
|
||||
local tx_raw_details
|
||||
|
||||
# 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 -r ".result")
|
||||
trace "[batchspend] txid=${txid}"
|
||||
|
||||
# Let's get transaction details on the spending wallet so that we have fee information
|
||||
tx_details=$(get_transaction ${txid} "spender")
|
||||
tx_raw_details=$(get_rawtransaction ${txid})
|
||||
|
||||
# Amounts and fees are negative when spending so we absolute those fields
|
||||
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 | fabs' | awk '{ printf "%.8f", $0 }')
|
||||
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=$(echo "${tx_details}" | jq '.result.fee | fabs' | awk '{ printf "%.8f", $0 }')
|
||||
# Sometimes raw tx are too long to be passed as paramater, so let's write
|
||||
# it to a temp file for it to be read by sqlite3 and then delete the file
|
||||
echo "${tx_raw_details}" > batchspend-rawtx-${txid}-$$.blob
|
||||
|
||||
# Let's insert the txid in our little DB -- then we'll already have it when receiving confirmation
|
||||
sql "INSERT OR IGNORE INTO tx (txid, hash, confirmations, timereceived, fee, size, vsize, is_replaceable, raw_tx) VALUES (\"${txid}\", ${tx_hash}, 0, ${tx_ts_firstseen}, ${fees}, ${tx_size}, ${tx_vsize}, ${tx_replaceable}, readfile('batchspend-rawtx-${txid}-$$.blob'))"
|
||||
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}\"}"
|
||||
|
||||
# Delete the temp file containing the raw tx (see above)
|
||||
rm batchspend-rawtx-${txid}-$$.blob
|
||||
else
|
||||
local message=$(echo "${response}" | jq -e ".error.message")
|
||||
data="{\"message\":${message}}"
|
||||
fi
|
||||
|
||||
trace "[batchspend] responding=${data}"
|
||||
echo "${data}"
|
||||
|
||||
return ${returncode}
|
||||
}
|
||||
|
||||
create_wallet() {
|
||||
trace "[Entering create_wallet()]"
|
||||
|
||||
@@ -416,4 +310,3 @@ create_wallet() {
|
||||
|
||||
return ${returncode}
|
||||
}
|
||||
|
||||
|
||||
@@ -52,12 +52,13 @@ watchrequest() {
|
||||
imported=0
|
||||
fi
|
||||
|
||||
sql "INSERT INTO watching (address, watching, callback0conf, callback1conf, imported, event_message) VALUES (\"${address}\", 1, ${cb0conf_url}, ${cb1conf_url}, ${imported}, ${event_message}) ON CONFLICT(address) DO UPDATE SET watching=1, callback0conf=excluded.callback0conf, calledback0conf=0, callback1conf=excluded.callback1conf, calledback1conf=0, event_message=excluded.event_message"
|
||||
sql "INSERT INTO watching (address, watching, callback0conf, callback1conf, imported, event_message) VALUES (\"${address}\", 1, ${cb0conf_url}, ${cb1conf_url}, ${imported}, ${event_message}) ON CONFLICT(address,callback0conf,callback1conf) DO UPDATE SET watching=1, event_message=${event_message}, calledback0conf=0, calledback1conf=0"
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
|
||||
if [ "${returncode}" -eq 0 ]; then
|
||||
inserted=1
|
||||
id_inserted=$(sql "SELECT id FROM watching WHERE address='${address}'")
|
||||
id_inserted=$(sql "SELECT id FROM watching WHERE address='${address}' AND callback0conf=${cb0conf_url} AND callback1conf=${cb1conf_url}")
|
||||
trace "[watchrequest] id_inserted: ${id_inserted}"
|
||||
else
|
||||
inserted=0
|
||||
@@ -78,15 +79,15 @@ watchrequest() {
|
||||
|
||||
result="{\"id\":\"${id_inserted}\",
|
||||
\"event\":\"watch\",
|
||||
\"imported\":\"${imported}\",
|
||||
\"inserted\":\"${inserted}\",
|
||||
\"imported\":${imported},
|
||||
\"inserted\":${inserted},
|
||||
\"address\":\"${address}\",
|
||||
\"unconfirmedCallbackURL\":${cb0conf_url},
|
||||
\"confirmedCallbackURL\":${cb1conf_url},
|
||||
\"estimatesmartfee2blocks\":\"${fees2blocks}\",
|
||||
\"estimatesmartfee6blocks\":\"${fees6blocks}\",
|
||||
\"estimatesmartfee36blocks\":\"${fees36blocks}\",
|
||||
\"estimatesmartfee144blocks\":\"${fees144blocks}\",
|
||||
\"estimatesmartfee2blocks\":${fees2blocks},
|
||||
\"estimatesmartfee6blocks\":${fees6blocks},
|
||||
\"estimatesmartfee36blocks\":${fees36blocks},
|
||||
\"estimatesmartfee144blocks\":${fees144blocks},
|
||||
\"eventMessage\":${event_message}}"
|
||||
trace "[watchrequest] responding=${result}"
|
||||
|
||||
@@ -270,7 +271,7 @@ insert_watches() {
|
||||
inserted_values="${inserted_values})"
|
||||
done
|
||||
|
||||
sql "INSERT INTO watching (address, watching, callback0conf, callback1conf, imported, watching_by_pub32_id, pub32_index) VALUES ${inserted_values} ON CONFLICT(address) DO UPDATE SET watching=1, callback0conf=excluded.callback0conf, calledback0conf=0, callback1conf=excluded.callback1conf, calledback1conf=0"
|
||||
sql "INSERT INTO watching (address, watching, callback0conf, callback1conf, imported, watching_by_pub32_id, pub32_index) VALUES ${inserted_values} ON CONFLICT(address,callback0conf,callback1conf) DO UPDATE SET watching=1, event_message=${event_message}, calledback0conf=0, calledback1conf=0"
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
|
||||
@@ -313,7 +314,7 @@ extend_watchers() {
|
||||
# we want to extend the watched addresses to 166 if our gap is 100 (default).
|
||||
trace "[extend_watchers] We have addresses to add to watchers!"
|
||||
|
||||
watchpub32 "${label}" "${pub32}" "${derivation_path}" $((${last_imported_n} + 1)) "${callback0conf}" "${callback1conf}" ${upgrade_to_n} > /dev/null
|
||||
watchpub32 "${label}" "${pub32}" "${derivation_path}" "$((${last_imported_n} + 1))" "${callback0conf}" "${callback1conf}" "${upgrade_to_n}" > /dev/null
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
else
|
||||
@@ -342,12 +343,13 @@ watchtxidrequest() {
|
||||
local result
|
||||
trace "[watchtxidrequest] Watch request on txid (${txid}), cb 1-conf (${cb1conf_url}) and cb x-conf (${cbxconf_url}) on ${nbxconf} confirmations."
|
||||
|
||||
sql "INSERT OR IGNORE INTO watching_by_txid (txid, watching, callback1conf, callbackxconf, nbxconf) VALUES (${txid}, 1, ${cb1conf_url}, ${cbxconf_url}, ${nbxconf})"
|
||||
sql "INSERT INTO watching_by_txid (txid, watching, callback1conf, callbackxconf, nbxconf) VALUES (${txid}, 1, ${cb1conf_url}, ${cbxconf_url}, ${nbxconf}) ON CONFLICT(txid, callback1conf, callbackxconf) DO UPDATE SET watching=1, nbxconf=${nbxconf}, calledback1conf=0, calledbackxconf=0"
|
||||
returncode=$?
|
||||
trace_rc ${returncode}
|
||||
|
||||
if [ "${returncode}" -eq 0 ]; then
|
||||
inserted=1
|
||||
id_inserted=$(sql "SELECT id FROM watching_by_txid WHERE txid=${txid}")
|
||||
id_inserted=$(sql "SELECT id FROM watching_by_txid WHERE txid=${txid} AND callback1conf=${cb1conf_url} AND callbackxconf=${cbxconf_url}")
|
||||
trace "[watchtxidrequest] id_inserted: ${id_inserted}"
|
||||
else
|
||||
inserted=0
|
||||
|
||||
Reference in New Issue
Block a user