commit
692578fd12
@ -1,5 +1,6 @@
|
|||||||
language: node_js
|
language: node_js
|
||||||
node_js:
|
node_js:
|
||||||
- 'v0.12.7'
|
- 'v0.12.7'
|
||||||
|
- 'v4'
|
||||||
install:
|
install:
|
||||||
- npm install
|
- npm install
|
||||||
|
|||||||
166
README.md
166
README.md
@ -18,9 +18,61 @@ The API endpoints will be available by default at: `http://localhost:3001/insigh
|
|||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- [Bitcore Node 0.2.x](https://github.com/bitpay/bitcore-node)
|
- [Bitcore Node 3.x](https://github.com/bitpay/bitcore-node)
|
||||||
|
|
||||||
**Note:** You can use an existing Bitcoin data directory, however `txindex` needs to be set to true in `bitcoin.conf`.
|
**Note:** You can use an existing Bitcoin data directory, however `txindex`, `addressindex`, `timestampindex` and `spentindex` needs to be set to true in `bitcoin.conf`, as well as a few other additional fields.
|
||||||
|
|
||||||
|
## Notes on Upgrading from v0.3
|
||||||
|
|
||||||
|
The unspent outputs format now has `satoshis` and `height`:
|
||||||
|
```
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"address":"mo9ncXisMeAoXwqcV5EWuyncbmCcQN4rVs",
|
||||||
|
"txid":"d5f8a96faccf79d4c087fa217627bb1120e83f8ea1a7d84b1de4277ead9bbac1",
|
||||||
|
"vout":0,
|
||||||
|
"scriptPubKey":"76a91453c0307d6851aa0ce7825ba883c6bd9ad242b48688ac",
|
||||||
|
"amount":0.000006,
|
||||||
|
"satoshis":600,
|
||||||
|
"confirmations":0,
|
||||||
|
"ts":1461349425
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "mo9ncXisMeAoXwqcV5EWuyncbmCcQN4rVs",
|
||||||
|
"txid": "bc9df3b92120feaee4edc80963d8ed59d6a78ea0defef3ec3cb374f2015bfc6e",
|
||||||
|
"vout": 1,
|
||||||
|
"scriptPubKey": "76a91453c0307d6851aa0ce7825ba883c6bd9ad242b48688ac",
|
||||||
|
"amount": 0.12345678,
|
||||||
|
"satoshis: 12345678,
|
||||||
|
"confirmations": 1,
|
||||||
|
"height": 300001
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
The `timestamp` property will only be set for unconfirmed transactions and `height` can be used for determining block order. The `confirmationsFromCache` is nolonger set or necessary, confirmation count is only cached for the time between blocks.
|
||||||
|
|
||||||
|
There is a new `GET` endpoint or raw blocks at `/rawblock/<blockHash>`:
|
||||||
|
|
||||||
|
Response format:
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"rawblock": "blockhexstring..."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
There are a few changes to the `GET` endpoint for `/addr/[:address]`:
|
||||||
|
|
||||||
|
- The list of txids in an address summary does not include orphaned transactions
|
||||||
|
- The txids will be sorted in block order
|
||||||
|
- The list of txids will be limited at 1000 txids
|
||||||
|
- There are two new query options "from" and "to" for pagination of the txids (e.g. `/addr/[:address]?from=1000&to=2000`)
|
||||||
|
|
||||||
|
Some additional general notes:
|
||||||
|
- The transaction history for an address will be sorted in block order
|
||||||
|
- The response for the `/sync` endpoint does not include `startTs` and `endTs` as the sync is no longer relevant as indexes are built in bitcoind.
|
||||||
|
- The endpoint for `/peer` is no longer relevant connection to bitcoind is via ZMQ.
|
||||||
|
- `/tx` endpoint results will now include block height, and spentTx related fields will be set to `null` if unspent.
|
||||||
|
- `/block` endpoint results does not include `confirmations` and will include `poolInfo`.
|
||||||
|
|
||||||
## Notes on Upgrading from v0.2
|
## Notes on Upgrading from v0.2
|
||||||
|
|
||||||
@ -61,10 +113,62 @@ Get block hash by height
|
|||||||
```
|
```
|
||||||
This would return:
|
This would return:
|
||||||
```
|
```
|
||||||
{"blockHash":"000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"}
|
{
|
||||||
|
"blockHash":"000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
|
||||||
|
}
|
||||||
```
|
```
|
||||||
which is the hash of the Genesis block (0 height)
|
which is the hash of the Genesis block (0 height)
|
||||||
|
|
||||||
|
|
||||||
|
### Raw Block
|
||||||
|
```
|
||||||
|
/insight-api/rawblock/[:blockHash]
|
||||||
|
/insight-api/rawblock/[:blockHeight]
|
||||||
|
```
|
||||||
|
|
||||||
|
This would return:
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"rawblock":"blockhexstring..."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Block Summaries
|
||||||
|
|
||||||
|
Get block summaries by date:
|
||||||
|
```
|
||||||
|
/insight-api/blocks?limit=3&blockDate=2016-04-22
|
||||||
|
```
|
||||||
|
|
||||||
|
Example response:
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"blocks": [
|
||||||
|
{
|
||||||
|
"height": 408495,
|
||||||
|
"size": 989237,
|
||||||
|
"hash": "00000000000000000108a1f4d4db839702d72f16561b1154600a26c453ecb378",
|
||||||
|
"time": 1461360083,
|
||||||
|
"txlength": 1695,
|
||||||
|
"poolInfo": {
|
||||||
|
"poolName": "BTCC Pool",
|
||||||
|
"url": "https://pool.btcc.com/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"length": 1,
|
||||||
|
"pagination": {
|
||||||
|
"next": "2016-04-23",
|
||||||
|
"prev": "2016-04-21",
|
||||||
|
"currentTs": 1461369599,
|
||||||
|
"current": "2016-04-22",
|
||||||
|
"isToday": true,
|
||||||
|
"more": true,
|
||||||
|
"moreTs": 1461369600
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Transaction
|
### Transaction
|
||||||
```
|
```
|
||||||
/insight-api/tx/[:txid]
|
/insight-api/tx/[:txid]
|
||||||
@ -75,8 +179,9 @@ which is the hash of the Genesis block (0 height)
|
|||||||
|
|
||||||
### Address
|
### Address
|
||||||
```
|
```
|
||||||
/insight-api/addr/[:addr][?noTxList=1&noCache=1]
|
/insight-api/addr/[:addr][?noTxList=1][&from=&to=]
|
||||||
/insight-api/addr/mmvP3mTe53qxHdPqXEvdu8WdC7GfQ2vmx5?noTxList=1
|
/insight-api/addr/mmvP3mTe53qxHdPqXEvdu8WdC7GfQ2vmx5?noTxList=1
|
||||||
|
/insight-api/addr/mmvP3mTe53qxHdPqXEvdu8WdC7GfQ2vmx5?from=1000&to=2000
|
||||||
```
|
```
|
||||||
|
|
||||||
### Address Properties
|
### Address Properties
|
||||||
@ -90,30 +195,31 @@ The response contains the value in Satoshis.
|
|||||||
|
|
||||||
### Unspent Outputs
|
### Unspent Outputs
|
||||||
```
|
```
|
||||||
/insight-api/addr/[:addr]/utxo[?noCache=1]
|
/insight-api/addr/[:addr]/utxo
|
||||||
```
|
```
|
||||||
Sample return:
|
Sample return:
|
||||||
``` json
|
```
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"address": "n2PuaAguxZqLddRbTnAoAuwKYgN2w2hZk7",
|
"address":"mo9ncXisMeAoXwqcV5EWuyncbmCcQN4rVs",
|
||||||
"txid": "dbfdc2a0d22a8282c4e7be0452d595695f3a39173bed4f48e590877382b112fc",
|
"txid":"d5f8a96faccf79d4c087fa217627bb1120e83f8ea1a7d84b1de4277ead9bbac1",
|
||||||
"vout": 0,
|
"vout":0,
|
||||||
"ts": 1401276201,
|
"scriptPubKey":"76a91453c0307d6851aa0ce7825ba883c6bd9ad242b48688ac",
|
||||||
"scriptPubKey": "76a914e50575162795cd77366fb80d728e3216bd52deac88ac",
|
"amount":0.000006,
|
||||||
"amount": 0.001,
|
"satoshis":600,
|
||||||
"confirmations": 3
|
"confirmations":0,
|
||||||
},
|
"ts":1461349425
|
||||||
{
|
},
|
||||||
"address": "n2PuaAguxZqLddRbTnAoAuwKYgN2w2hZk7",
|
{
|
||||||
"txid": "e2b82af55d64f12fd0dd075d0922ee7d6a300f58fe60a23cbb5831b31d1d58b4",
|
"address": "mo9ncXisMeAoXwqcV5EWuyncbmCcQN4rVs",
|
||||||
"vout": 0,
|
"txid": "bc9df3b92120feaee4edc80963d8ed59d6a78ea0defef3ec3cb374f2015bfc6e",
|
||||||
"ts": 1401226410,
|
"vout": 1,
|
||||||
"scriptPubKey": "76a914e50575162795cd77366fb80d728e3216bd52deac88ac",
|
"scriptPubKey": "76a91453c0307d6851aa0ce7825ba883c6bd9ad242b48688ac",
|
||||||
"amount": 0.001,
|
"amount": 0.12345678,
|
||||||
"confirmation": 6,
|
"satoshis: 12345678,
|
||||||
"confirmationsFromCache": true
|
"confirmations": 1,
|
||||||
}
|
"height": 300001
|
||||||
|
}
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -226,7 +332,7 @@ POST response:
|
|||||||
/insight-api/sync
|
/insight-api/sync
|
||||||
```
|
```
|
||||||
|
|
||||||
### Live Network P2P Data Sync Status (Bitcoind runs in the same process)
|
### Live Network P2P Data Sync Status
|
||||||
```
|
```
|
||||||
/insight-api/peer
|
/insight-api/peer
|
||||||
```
|
```
|
||||||
@ -255,7 +361,7 @@ The web socket API is served using [socket.io](http://socket.io).
|
|||||||
|
|
||||||
The following are the events published by insight:
|
The following are the events published by insight:
|
||||||
|
|
||||||
'tx': new transaction received from network. This event is published in the 'inv' room. Data will be a app/models/Transaction object.
|
`tx`: new transaction received from network. This event is published in the 'inv' room. Data will be a app/models/Transaction object.
|
||||||
Sample output:
|
Sample output:
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
@ -266,7 +372,7 @@ Sample output:
|
|||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
'block': new block received from network. This event is published in the 'inv' room. Data will be a app/models/Block object.
|
`block`: new block received from network. This event is published in the `inv` room. Data will be a app/models/Block object.
|
||||||
Sample output:
|
Sample output:
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
@ -276,9 +382,9 @@ Sample output:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
'<bitcoinAddress>': new transaction concerning <bitcoinAddress> received from network. This event is published in the '<bitcoinAddress>' room.
|
`<bitcoinAddress>`: new transaction concerning <bitcoinAddress> received from network. This event is published in the `<bitcoinAddress>` room.
|
||||||
|
|
||||||
'status': every 1% increment on the sync task, this event will be triggered. This event is published in the 'sync' room.
|
`status`: every 1% increment on the sync task, this event will be triggered. This event is published in the `sync` room.
|
||||||
|
|
||||||
Sample output:
|
Sample output:
|
||||||
```
|
```
|
||||||
|
|||||||
@ -1,23 +1,30 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var common = require('./common');
|
|
||||||
var bitcore = require('bitcore-lib');
|
var bitcore = require('bitcore-lib');
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
var TxController = require('./transactions');
|
var TxController = require('./transactions');
|
||||||
|
var Common = require('./common');
|
||||||
|
|
||||||
function AddressController(node) {
|
function AddressController(node) {
|
||||||
this.node = node;
|
this.node = node;
|
||||||
this.txController = new TxController(node);
|
this.txController = new TxController(node);
|
||||||
|
this.common = new Common({log: this.node.log});
|
||||||
}
|
}
|
||||||
|
|
||||||
AddressController.prototype.show = function(req, res) {
|
AddressController.prototype.show = function(req, res) {
|
||||||
|
var self = this;
|
||||||
var options = {
|
var options = {
|
||||||
noTxList: parseInt(req.query.noTxList)
|
noTxList: parseInt(req.query.noTxList)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (req.query.from && req.query.to) {
|
||||||
|
options.from = parseInt(req.query.from);
|
||||||
|
options.to = parseInt(req.query.to);
|
||||||
|
}
|
||||||
|
|
||||||
this.getAddressSummary(req.addr, options, function(err, data) {
|
this.getAddressSummary(req.addr, options, function(err, data) {
|
||||||
if(err) {
|
if(err) {
|
||||||
return common.handleErrors(err, res);
|
return self.common.handleErrors(err, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
res.jsonp(data);
|
res.jsonp(data);
|
||||||
@ -41,9 +48,10 @@ AddressController.prototype.unconfirmedBalance = function(req, res) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
AddressController.prototype.addressSummarySubQuery = function(req, res, param) {
|
AddressController.prototype.addressSummarySubQuery = function(req, res, param) {
|
||||||
|
var self = this;
|
||||||
this.getAddressSummary(req.addr, {}, function(err, data) {
|
this.getAddressSummary(req.addr, {}, function(err, data) {
|
||||||
if(err) {
|
if(err) {
|
||||||
return common.handleErrors(err, res);
|
return self.common.handleErrors(err, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
res.jsonp(data[param]);
|
res.jsonp(data[param]);
|
||||||
@ -51,7 +59,6 @@ AddressController.prototype.addressSummarySubQuery = function(req, res, param) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
AddressController.prototype.getAddressSummary = function(address, options, callback) {
|
AddressController.prototype.getAddressSummary = function(address, options, callback) {
|
||||||
var self = this;
|
|
||||||
|
|
||||||
this.node.getAddressSummary(address, options, function(err, summary) {
|
this.node.getAddressSummary(address, options, function(err, summary) {
|
||||||
if(err) {
|
if(err) {
|
||||||
@ -90,11 +97,12 @@ AddressController.prototype.checkAddrs = function(req, res, next) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.check(req, res, next, req.addrs);
|
this.check(req, res, next, req.addrs);
|
||||||
}
|
};
|
||||||
|
|
||||||
AddressController.prototype.check = function(req, res, next, addresses) {
|
AddressController.prototype.check = function(req, res, next, addresses) {
|
||||||
|
var self = this;
|
||||||
if(!addresses.length || !addresses[0]) {
|
if(!addresses.length || !addresses[0]) {
|
||||||
return common.handleErrors({
|
return self.common.handleErrors({
|
||||||
message: 'Must include address',
|
message: 'Must include address',
|
||||||
code: 1
|
code: 1
|
||||||
}, res);
|
}, res);
|
||||||
@ -104,7 +112,7 @@ AddressController.prototype.check = function(req, res, next, addresses) {
|
|||||||
try {
|
try {
|
||||||
var a = new bitcore.Address(addresses[i]);
|
var a = new bitcore.Address(addresses[i]);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
return common.handleErrors({
|
return self.common.handleErrors({
|
||||||
message: 'Invalid address: ' + e.message,
|
message: 'Invalid address: ' + e.message,
|
||||||
code: 1
|
code: 1
|
||||||
}, res);
|
}, res);
|
||||||
@ -117,60 +125,67 @@ AddressController.prototype.check = function(req, res, next, addresses) {
|
|||||||
AddressController.prototype.utxo = function(req, res) {
|
AddressController.prototype.utxo = function(req, res) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
this.node.getUnspentOutputs(req.addr, true, function(err, utxos) {
|
this.node.getAddressUnspentOutputs(req.addr, {}, function(err, utxos) {
|
||||||
if(err && err instanceof self.node.errors.NoOutputs) {
|
if(err) {
|
||||||
|
return self.common.handleErrors(err, res);
|
||||||
|
} else if (!utxos.length) {
|
||||||
return res.jsonp([]);
|
return res.jsonp([]);
|
||||||
} else if(err) {
|
|
||||||
return common.handleErrors(err, res);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
res.jsonp(utxos.map(self.transformUtxo.bind(self)));
|
res.jsonp(utxos.map(self.transformUtxo.bind(self)));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
AddressController.prototype.multiutxo = function(req, res) {
|
AddressController.prototype.multiutxo = function(req, res) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
this.node.getAddressUnspentOutputs(req.addrs, true, function(err, utxos) {
|
||||||
this.node.getUnspentOutputs(req.addrs, true, function(err, utxos) {
|
if(err && err.code === -5) {
|
||||||
if(err && err instanceof self.node.errors.NoOutputs) {
|
|
||||||
return res.jsonp([]);
|
return res.jsonp([]);
|
||||||
} else if(err) {
|
} else if(err) {
|
||||||
return common.handleErrors(err, res);
|
return self.common.handleErrors(err, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
res.jsonp(utxos.map(self.transformUtxo.bind(self)));
|
res.jsonp(utxos.map(self.transformUtxo.bind(self)));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
AddressController.prototype.transformUtxo = function(utxo) {
|
AddressController.prototype.transformUtxo = function(utxoArg) {
|
||||||
return {
|
var utxo = {
|
||||||
address: utxo.address,
|
address: utxoArg.address,
|
||||||
txid: utxo.txid,
|
txid: utxoArg.txid,
|
||||||
vout: utxo.outputIndex,
|
vout: utxoArg.outputIndex,
|
||||||
ts: utxo.timestamp ? parseInt(utxo.timestamp) : Date.now(),
|
scriptPubKey: utxoArg.script,
|
||||||
scriptPubKey: utxo.script,
|
amount: utxoArg.satoshis / 1e8,
|
||||||
amount: utxo.satoshis / 1e8,
|
satoshis: utxoArg.satoshis
|
||||||
confirmations: utxo.confirmations
|
|
||||||
};
|
};
|
||||||
|
if (utxoArg.height && utxoArg.height > 0) {
|
||||||
|
utxo.height = utxoArg.height;
|
||||||
|
utxo.confirmations = this.node.services.bitcoind.height - utxoArg.height + 1;
|
||||||
|
} else {
|
||||||
|
utxo.confirmations = 0;
|
||||||
|
}
|
||||||
|
if (utxoArg.timestamp) {
|
||||||
|
utxo.ts = utxoArg.timestamp;
|
||||||
|
}
|
||||||
|
return utxo;
|
||||||
};
|
};
|
||||||
|
|
||||||
AddressController.prototype.multitxs = function(req, res, next) {
|
AddressController.prototype.multitxs = function(req, res, next) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
var options = {
|
var options = {
|
||||||
from: req.query.from || req.body.from || 0
|
from: parseInt(req.query.from) || parseInt(req.body.from) || 0
|
||||||
};
|
};
|
||||||
|
|
||||||
options.to = req.query.to || req.body.to || options.from + 10;
|
options.to = parseInt(req.query.to) || parseInt(req.body.to) || parseInt(options.from) + 10;
|
||||||
|
|
||||||
self.node.getAddressHistory(req.addrs, options, function(err, result) {
|
self.node.getAddressHistory(req.addrs, options, function(err, result) {
|
||||||
if(err) {
|
if(err) {
|
||||||
return common.handleErrors(err, res);
|
return self.common.handleErrors(err, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.transformAddressHistoryForMultiTxs(result.items, function(err, items) {
|
self.transformAddressHistoryForMultiTxs(result.items, function(err, items) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return common.handleErrors(err, res);
|
return self.common.handleErrors(err, res);
|
||||||
}
|
}
|
||||||
res.jsonp({
|
res.jsonp({
|
||||||
totalItems: result.totalCount,
|
totalItems: result.totalCount,
|
||||||
|
|||||||
229
lib/blocks.js
229
lib/blocks.js
@ -1,14 +1,20 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var common = require('./common');
|
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
var bitcore = require('bitcore-lib');
|
var bitcore = require('bitcore-lib');
|
||||||
|
var _ = bitcore.deps._;
|
||||||
var pools = require('../pools.json');
|
var pools = require('../pools.json');
|
||||||
var BN = bitcore.crypto.BN;
|
var BN = bitcore.crypto.BN;
|
||||||
|
var LRU = require('lru-cache');
|
||||||
|
var Common = require('./common');
|
||||||
|
|
||||||
function BlockController(node) {
|
function BlockController(options) {
|
||||||
var self = this;
|
var self = this;
|
||||||
this.node = node;
|
this.node = options.node;
|
||||||
|
|
||||||
|
this.blockSummaryCache = LRU(options.blockSummaryCacheSize || BlockController.DEFAULT_BLOCKSUMMARY_CACHE_SIZE);
|
||||||
|
this.blockCacheConfirmations = 6;
|
||||||
|
this.blockCache = LRU(options.blockCacheSize || BlockController.DEFAULT_BLOCK_CACHE_SIZE);
|
||||||
|
|
||||||
this.poolStrings = {};
|
this.poolStrings = {};
|
||||||
pools.forEach(function(pool) {
|
pools.forEach(function(pool) {
|
||||||
@ -19,30 +25,93 @@ function BlockController(node) {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.common = new Common({log: this.node.log});
|
||||||
}
|
}
|
||||||
|
|
||||||
var BLOCK_LIMIT = 200;
|
var BLOCK_LIMIT = 200;
|
||||||
|
|
||||||
|
BlockController.DEFAULT_BLOCKSUMMARY_CACHE_SIZE = 1000000;
|
||||||
|
BlockController.DEFAULT_BLOCK_CACHE_SIZE = 1000;
|
||||||
|
|
||||||
|
function isHexadecimal(hash) {
|
||||||
|
if (!_.isString(hash)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return /^[0-9a-fA-F]+$/.test(hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockController.prototype.checkBlockHash = function(req, res, next) {
|
||||||
|
var self = this;
|
||||||
|
var hash = req.params.blockHash;
|
||||||
|
if (hash.length < 64 || !isHexadecimal(hash)) {
|
||||||
|
return self.common.handleErrors(null, res);
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find block by hash ...
|
* Find block by hash ...
|
||||||
*/
|
*/
|
||||||
BlockController.prototype.block = function(req, res, next, hash) {
|
BlockController.prototype.block = function(req, res, next) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
var hash = req.params.blockHash;
|
||||||
|
var blockCached = self.blockCache.get(hash);
|
||||||
|
|
||||||
this.node.getBlock(hash, function(err, block) {
|
if (blockCached) {
|
||||||
if(err && err.message === 'Block not found.') {
|
blockCached.confirmations = self.node.services.bitcoind.height - blockCached.height + 1;
|
||||||
// TODO libbitcoind should pass an instance of errors.Block.NotFound
|
req.block = blockCached;
|
||||||
return common.handleErrors(null, res);
|
next();
|
||||||
|
} else {
|
||||||
|
self.node.getBlock(hash, function(err, block) {
|
||||||
|
if((err && err.code === -5) || (err && err.code === -8)) {
|
||||||
|
return self.common.handleErrors(null, res);
|
||||||
|
} else if(err) {
|
||||||
|
return self.common.handleErrors(err, res);
|
||||||
|
}
|
||||||
|
self.node.services.bitcoind.getBlockHeader(hash, function(err, info) {
|
||||||
|
if (err) {
|
||||||
|
return self.common.handleErrors(err, res);
|
||||||
|
}
|
||||||
|
var blockResult = self.transformBlock(block, info);
|
||||||
|
if (blockResult.confirmations >= self.blockCacheConfirmations) {
|
||||||
|
self.blockCache.set(hash, blockResult);
|
||||||
|
}
|
||||||
|
req.block = blockResult;
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find rawblock by hash and height...
|
||||||
|
*/
|
||||||
|
BlockController.prototype.rawBlock = function(req, res, next) {
|
||||||
|
var self = this;
|
||||||
|
var blockHash = req.params.blockHash;
|
||||||
|
|
||||||
|
self.node.getRawBlock(blockHash, function(err, blockBuffer) {
|
||||||
|
if((err && err.code === -5) || (err && err.code === -8)) {
|
||||||
|
return self.common.handleErrors(null, res);
|
||||||
} else if(err) {
|
} else if(err) {
|
||||||
return common.handleErrors(err, res);
|
return self.common.handleErrors(err, res);
|
||||||
}
|
}
|
||||||
|
req.rawBlock = {
|
||||||
var info = self.node.services.bitcoind.getBlockIndex(hash);
|
rawblock: blockBuffer.toString('hex')
|
||||||
info.isMainChain = self.node.services.bitcoind.isMainChain(hash);
|
};
|
||||||
|
|
||||||
req.block = self.transformBlock(block, info);
|
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
BlockController.prototype._normalizePrevHash = function(hash) {
|
||||||
|
// TODO fix bitcore to give back null instead of null hash
|
||||||
|
if (hash !== '0000000000000000000000000000000000000000000000000000000000000000') {
|
||||||
|
return hash;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
BlockController.prototype.transformBlock = function(block, info) {
|
BlockController.prototype.transformBlock = function(block, info) {
|
||||||
@ -52,7 +121,6 @@ BlockController.prototype.transformBlock = function(block, info) {
|
|||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
hash: block.hash,
|
hash: block.hash,
|
||||||
confirmations: this.node.services.db.tip.__height - info.height + 1,
|
|
||||||
size: block.toBuffer().length,
|
size: block.toBuffer().length,
|
||||||
height: info.height,
|
height: info.height,
|
||||||
version: blockObj.header.version,
|
version: blockObj.header.version,
|
||||||
@ -63,10 +131,11 @@ BlockController.prototype.transformBlock = function(block, info) {
|
|||||||
bits: blockObj.header.bits.toString(16),
|
bits: blockObj.header.bits.toString(16),
|
||||||
difficulty: block.header.getDifficulty(),
|
difficulty: block.header.getDifficulty(),
|
||||||
chainwork: info.chainWork,
|
chainwork: info.chainWork,
|
||||||
previousblockhash: blockObj.header.prevHash,
|
confirmations: info.confirmations,
|
||||||
nextblockhash: this.node.services.bitcoind.getNextBlockHash(block.hash),
|
previousblockhash: this._normalizePrevHash(blockObj.header.prevHash),
|
||||||
|
nextblockhash: info.nextHash,
|
||||||
reward: this.getBlockReward(info.height) / 1e8,
|
reward: this.getBlockReward(info.height) / 1e8,
|
||||||
isMainChain: info.isMainChain,
|
isMainChain: (info.confirmations !== -1),
|
||||||
poolInfo: this.getPoolInfo(block)
|
poolInfo: this.getPoolInfo(block)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -80,15 +149,83 @@ BlockController.prototype.show = function(req, res) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
BlockController.prototype.blockIndex = function(req, res, next, height) {
|
BlockController.prototype.showRaw = function(req, res) {
|
||||||
var info = this.node.services.bitcoind.getBlockIndex(parseInt(height));
|
if (req.rawBlock) {
|
||||||
if(!info) {
|
res.jsonp(req.rawBlock);
|
||||||
return common.handleErrors(null, res);
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
BlockController.prototype.blockIndex = function(req, res) {
|
||||||
|
var self = this;
|
||||||
|
var height = req.params.height;
|
||||||
|
this.node.services.bitcoind.getBlockHeader(parseInt(height), function(err, info) {
|
||||||
|
if (err) {
|
||||||
|
return self.common.handleErrors(err, res);
|
||||||
|
}
|
||||||
|
res.jsonp({
|
||||||
|
blockHash: info.hash
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
BlockController.prototype._getBlockSummary = function(hash, moreTimestamp, next) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
function finish(result) {
|
||||||
|
if (moreTimestamp > result.time) {
|
||||||
|
moreTimestamp = result.time;
|
||||||
|
}
|
||||||
|
return next(null, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
res.jsonp({
|
var summaryCache = self.blockSummaryCache.get(hash);
|
||||||
blockHash: info.hash
|
|
||||||
});
|
if (summaryCache) {
|
||||||
|
finish(summaryCache);
|
||||||
|
} else {
|
||||||
|
self.node.services.bitcoind.getRawBlock(hash, function(err, blockBuffer) {
|
||||||
|
if (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
var br = new bitcore.encoding.BufferReader(blockBuffer);
|
||||||
|
|
||||||
|
// take a shortcut to get number of transactions and the blocksize.
|
||||||
|
// Also reads the coinbase transaction and only that.
|
||||||
|
// Old code parsed all transactions in every block _and_ then encoded
|
||||||
|
// them all back together to get the binary size of the block.
|
||||||
|
// FIXME: This code might still read the whole block. Fixing that
|
||||||
|
// would require changes in bitcore-node.
|
||||||
|
var header = bitcore.BlockHeader.fromBufferReader(br);
|
||||||
|
var info = {};
|
||||||
|
var txlength = br.readVarintNum();
|
||||||
|
info.transactions = [bitcore.Transaction().fromBufferReader(br)];
|
||||||
|
|
||||||
|
self.node.services.bitcoind.getBlockHeader(hash, function(err, blockHeader) {
|
||||||
|
if (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
var height = blockHeader.height;
|
||||||
|
|
||||||
|
var summary = {
|
||||||
|
height: height,
|
||||||
|
size: blockBuffer.length,
|
||||||
|
hash: hash,
|
||||||
|
time: header.time,
|
||||||
|
txlength: txlength,
|
||||||
|
poolInfo: self.getPoolInfo(info)
|
||||||
|
};
|
||||||
|
|
||||||
|
var confirmations = self.node.services.bitcoind.height - height + 1;
|
||||||
|
if (confirmations >= self.blockCacheConfirmations) {
|
||||||
|
self.blockSummaryCache.set(hash, summary);
|
||||||
|
}
|
||||||
|
|
||||||
|
finish(summary);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// List blocks by date
|
// List blocks by date
|
||||||
@ -103,7 +240,7 @@ BlockController.prototype.list = function(req, res) {
|
|||||||
dateStr = req.query.blockDate;
|
dateStr = req.query.blockDate;
|
||||||
var datePattern = /\d{4}-\d{2}-\d{2}/;
|
var datePattern = /\d{4}-\d{2}-\d{2}/;
|
||||||
if(!datePattern.test(dateStr)) {
|
if(!datePattern.test(dateStr)) {
|
||||||
return common.handleErrors(new Error('Please use yyyy-mm-dd format'), res);
|
return self.common.handleErrors(new Error('Please use yyyy-mm-dd format'), res);
|
||||||
}
|
}
|
||||||
|
|
||||||
isToday = dateStr === todayStr;
|
isToday = dateStr === todayStr;
|
||||||
@ -120,13 +257,15 @@ BlockController.prototype.list = function(req, res) {
|
|||||||
var next = lte ? this.formatTimestamp(new Date(lte * 1000)) : null;
|
var next = lte ? this.formatTimestamp(new Date(lte * 1000)) : null;
|
||||||
var limit = parseInt(req.query.limit || BLOCK_LIMIT);
|
var limit = parseInt(req.query.limit || BLOCK_LIMIT);
|
||||||
var more = false;
|
var more = false;
|
||||||
var moreTs = lte;
|
var moreTimestamp = lte;
|
||||||
|
|
||||||
self.node.services.db.getBlockHashesByTimestamp(lte, gte, function(err, hashes) {
|
self.node.services.bitcoind.getBlockHashesByTimestamp(lte, gte, function(err, hashes) {
|
||||||
if(err) {
|
if(err) {
|
||||||
return common.handleErrors(err, res);
|
return self.common.handleErrors(err, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hashes.reverse();
|
||||||
|
|
||||||
if(hashes.length > limit) {
|
if(hashes.length > limit) {
|
||||||
more = true;
|
more = true;
|
||||||
hashes = hashes.slice(0, limit);
|
hashes = hashes.slice(0, limit);
|
||||||
@ -135,41 +274,19 @@ BlockController.prototype.list = function(req, res) {
|
|||||||
async.mapSeries(
|
async.mapSeries(
|
||||||
hashes,
|
hashes,
|
||||||
function(hash, next) {
|
function(hash, next) {
|
||||||
self.node.getBlock(hash, function(err, block) {
|
self._getBlockSummary(hash, moreTimestamp, next);
|
||||||
if(err) {
|
|
||||||
return next(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
var info = self.node.services.bitcoind.getBlockIndex(hash);
|
|
||||||
block.__height = info.height;
|
|
||||||
|
|
||||||
if(moreTs > block.header.timestamp) {
|
|
||||||
moreTs = block.header.timestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
return next(null, block);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
function(err, blocks) {
|
function(err, blocks) {
|
||||||
if(err) {
|
if(err) {
|
||||||
return common.handleErrors(err, res);
|
return self.common.handleErrors(err, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
blocks.sort(function(a, b) {
|
blocks.sort(function(a, b) {
|
||||||
return b.__height - a.__height;
|
return b.height - a.height;
|
||||||
});
|
});
|
||||||
|
|
||||||
var data = {
|
var data = {
|
||||||
blocks: blocks.map(function(block) {
|
blocks: blocks,
|
||||||
return {
|
|
||||||
height: block.__height,
|
|
||||||
size: block.toBuffer().length,
|
|
||||||
hash: block.hash,
|
|
||||||
time: block.header.time,
|
|
||||||
txlength: block.transactions.length,
|
|
||||||
poolInfo: self.getPoolInfo(block)
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
length: blocks.length,
|
length: blocks.length,
|
||||||
pagination: {
|
pagination: {
|
||||||
next: next,
|
next: next,
|
||||||
@ -182,7 +299,7 @@ BlockController.prototype.list = function(req, res) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if(more) {
|
if(more) {
|
||||||
data.pagination.moreTs = moreTs;
|
data.pagination.moreTs = moreTimestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
res.jsonp(data);
|
res.jsonp(data);
|
||||||
|
|||||||
@ -1,19 +1,24 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
exports.notReady = function (err, res, p) {
|
function Common(options) {
|
||||||
|
this.log = options.log;
|
||||||
|
}
|
||||||
|
|
||||||
|
Common.prototype.notReady = function (err, res, p) {
|
||||||
res.status(503).send('Server not yet ready. Sync Percentage:' + p);
|
res.status(503).send('Server not yet ready. Sync Percentage:' + p);
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.handleErrors = function (err, res) {
|
Common.prototype.handleErrors = function (err, res) {
|
||||||
if (err) {
|
if (err) {
|
||||||
if (err.code) {
|
if (err.code) {
|
||||||
res.status(400).send(err.message + '. Code:' + err.code);
|
res.status(400).send(err.message + '. Code:' + err.code);
|
||||||
}
|
} else {
|
||||||
else {
|
this.log.error(err.stack);
|
||||||
res.status(503).send(err.message);
|
res.status(503).send(err.message);
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
res.status(404).send('Not found');
|
res.status(404).send('Not found');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
module.exports = Common;
|
||||||
|
|||||||
102
lib/index.js
102
lib/index.js
@ -1,6 +1,8 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
var Writable = require('stream').Writable;
|
||||||
var bodyParser = require('body-parser');
|
var bodyParser = require('body-parser');
|
||||||
|
var compression = require('compression');
|
||||||
var BaseService = require('./service');
|
var BaseService = require('./service');
|
||||||
var inherits = require('util').inherits;
|
var inherits = require('util').inherits;
|
||||||
var BlockController = require('./blocks');
|
var BlockController = require('./blocks');
|
||||||
@ -10,6 +12,8 @@ var StatusController = require('./status');
|
|||||||
var MessagesController = require('./messages');
|
var MessagesController = require('./messages');
|
||||||
var UtilsController = require('./utils');
|
var UtilsController = require('./utils');
|
||||||
var CurrencyController = require('./currency');
|
var CurrencyController = require('./currency');
|
||||||
|
var RateLimiter = require('./ratelimiter');
|
||||||
|
var morgan = require('morgan');
|
||||||
var bitcore = require('bitcore-lib');
|
var bitcore = require('bitcore-lib');
|
||||||
var _ = bitcore.deps._;
|
var _ = bitcore.deps._;
|
||||||
var $ = bitcore.util.preconditions;
|
var $ = bitcore.util.preconditions;
|
||||||
@ -42,6 +46,9 @@ var InsightAPI = function(options) {
|
|||||||
this.cacheShortSeconds = options.cacheShortSeconds;
|
this.cacheShortSeconds = options.cacheShortSeconds;
|
||||||
this.cacheLongSeconds = options.cacheLongSeconds;
|
this.cacheLongSeconds = options.cacheLongSeconds;
|
||||||
|
|
||||||
|
this.blockSummaryCacheSize = options.blockSummaryCacheSize || BlockController.DEFAULT_BLOCKSUMMARY_CACHE_SIZE;
|
||||||
|
this.blockCacheSize = options.blockCacheSize || BlockController.DEFAULT_BLOCK_CACHE_SIZE;
|
||||||
|
|
||||||
if (!_.isUndefined(options.routePrefix)) {
|
if (!_.isUndefined(options.routePrefix)) {
|
||||||
this.routePrefix = options.routePrefix;
|
this.routePrefix = options.routePrefix;
|
||||||
} else {
|
} else {
|
||||||
@ -51,7 +58,7 @@ var InsightAPI = function(options) {
|
|||||||
this.txController = new TxController(this.node);
|
this.txController = new TxController(this.node);
|
||||||
};
|
};
|
||||||
|
|
||||||
InsightAPI.dependencies = ['address', 'web'];
|
InsightAPI.dependencies = ['bitcoind', 'web'];
|
||||||
|
|
||||||
inherits(InsightAPI, BaseService);
|
inherits(InsightAPI, BaseService);
|
||||||
|
|
||||||
@ -80,13 +87,55 @@ InsightAPI.prototype.getRoutePrefix = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
InsightAPI.prototype.start = function(callback) {
|
InsightAPI.prototype.start = function(callback) {
|
||||||
this.node.services.bitcoind.on('tx', this.transactionHandler.bind(this));
|
this.node.services.bitcoind.on('tx', this.transactionEventHandler.bind(this));
|
||||||
|
this.node.services.bitcoind.on('block', this.blockEventHandler.bind(this));
|
||||||
setImmediate(callback);
|
setImmediate(callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
InsightAPI.prototype.createLogInfoStream = function() {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
function Log(options) {
|
||||||
|
Writable.call(this, options);
|
||||||
|
}
|
||||||
|
inherits(Log, Writable);
|
||||||
|
|
||||||
|
Log.prototype._write = function (chunk, enc, callback) {
|
||||||
|
self.node.log.info(chunk.slice(0, chunk.length - 1)); // remove new line and pass to logger
|
||||||
|
callback();
|
||||||
|
};
|
||||||
|
var stream = new Log();
|
||||||
|
|
||||||
|
return stream;
|
||||||
|
};
|
||||||
|
|
||||||
|
InsightAPI.prototype.getRemoteAddress = function(req) {
|
||||||
|
if (req.headers['cf-connecting-ip']) {
|
||||||
|
return req.headers['cf-connecting-ip'];
|
||||||
|
}
|
||||||
|
return req.socket.remoteAddress;
|
||||||
|
};
|
||||||
|
|
||||||
InsightAPI.prototype.setupRoutes = function(app) {
|
InsightAPI.prototype.setupRoutes = function(app) {
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
//Enable rate limiter
|
||||||
|
var limiter = new RateLimiter({node: this.node});
|
||||||
|
app.use(limiter.middleware());
|
||||||
|
|
||||||
|
//Setup logging
|
||||||
|
morgan.token('remote-forward-addr', function(req){
|
||||||
|
return self.getRemoteAddress(req);
|
||||||
|
});
|
||||||
|
var logFormat = ':remote-forward-addr ":method :url" :status :res[content-length] :response-time ":user-agent" ';
|
||||||
|
var logStream = this.createLogInfoStream();
|
||||||
|
app.use(morgan(logFormat, {stream: logStream}));
|
||||||
|
|
||||||
|
//Enable compression
|
||||||
|
app.use(compression());
|
||||||
|
|
||||||
|
//Enable urlencoded data
|
||||||
app.use(bodyParser.urlencoded({extended: true}));
|
app.use(bodyParser.urlencoded({extended: true}));
|
||||||
|
|
||||||
//Enable CORS
|
//Enable CORS
|
||||||
@ -97,19 +146,26 @@ InsightAPI.prototype.setupRoutes = function(app) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
//Block routes
|
//Block routes
|
||||||
var blocks = new BlockController(this.node);
|
var blockOptions = {
|
||||||
|
node: this.node,
|
||||||
|
blockSummaryCacheSize: this.blockSummaryCacheSize,
|
||||||
|
blockCacheSize: this.blockCacheSize
|
||||||
|
};
|
||||||
|
var blocks = new BlockController(blockOptions);
|
||||||
app.get('/blocks', this.cacheShort(), blocks.list.bind(blocks));
|
app.get('/blocks', this.cacheShort(), blocks.list.bind(blocks));
|
||||||
|
|
||||||
|
app.get('/block/:blockHash', this.cacheShort(), blocks.checkBlockHash.bind(blocks), blocks.show.bind(blocks));
|
||||||
app.get('/block/:blockHash', this.cacheLong(), blocks.show.bind(blocks));
|
|
||||||
app.param('blockHash', blocks.block.bind(blocks));
|
app.param('blockHash', blocks.block.bind(blocks));
|
||||||
|
|
||||||
app.get('/block-index/:height', this.cacheLong(), blocks.blockIndex.bind(blocks));
|
app.get('/rawblock/:blockHash', this.cacheLong(), blocks.checkBlockHash.bind(blocks), blocks.showRaw.bind(blocks));
|
||||||
|
app.param('blockHash', blocks.rawBlock.bind(blocks));
|
||||||
|
|
||||||
|
app.get('/block-index/:height', this.cacheShort(), blocks.blockIndex.bind(blocks));
|
||||||
app.param('height', blocks.blockIndex.bind(blocks));
|
app.param('height', blocks.blockIndex.bind(blocks));
|
||||||
|
|
||||||
// Transaction routes
|
// Transaction routes
|
||||||
var transactions = new TxController(this.node);
|
var transactions = new TxController(this.node);
|
||||||
app.get('/tx/:txid', this.cacheLong(), transactions.show.bind(transactions));
|
app.get('/tx/:txid', this.cacheShort(), transactions.show.bind(transactions));
|
||||||
app.param('txid', transactions.transaction.bind(transactions));
|
app.param('txid', transactions.transaction.bind(transactions));
|
||||||
app.get('/txs', this.cacheShort(), transactions.list.bind(transactions));
|
app.get('/txs', this.cacheShort(), transactions.list.bind(transactions));
|
||||||
app.post('/tx/send', transactions.send.bind(transactions));
|
app.post('/tx/send', transactions.send.bind(transactions));
|
||||||
@ -156,6 +212,15 @@ InsightAPI.prototype.setupRoutes = function(app) {
|
|||||||
});
|
});
|
||||||
app.get('/currency', currency.index.bind(currency));
|
app.get('/currency', currency.index.bind(currency));
|
||||||
|
|
||||||
|
// Not Found
|
||||||
|
app.use(function(req, res) {
|
||||||
|
res.status(404).jsonp({
|
||||||
|
status: 404,
|
||||||
|
url: req.originalUrl,
|
||||||
|
error: 'Not found'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
InsightAPI.prototype.getPublishEvents = function() {
|
InsightAPI.prototype.getPublishEvents = function() {
|
||||||
@ -170,25 +235,18 @@ InsightAPI.prototype.getPublishEvents = function() {
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
InsightAPI.prototype.blockHandler = function(block, add, callback) {
|
InsightAPI.prototype.blockEventHandler = function(hashBuffer) {
|
||||||
// Notify inv subscribers
|
// Notify inv subscribers
|
||||||
for (var i = 0; i < this.subscriptions.inv.length; i++) {
|
for (var i = 0; i < this.subscriptions.inv.length; i++) {
|
||||||
this.subscriptions.inv[i].emit('block', block.hash);
|
this.subscriptions.inv[i].emit('block', hashBuffer.toString('hex'));
|
||||||
}
|
}
|
||||||
|
|
||||||
setImmediate(function() {
|
|
||||||
callback(null, []);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
InsightAPI.prototype.transactionEventHandler = function(txBuffer) {
|
||||||
|
var tx = new Transaction().fromBuffer(txBuffer);
|
||||||
|
var result = this.txController.transformInvTransaction(tx);
|
||||||
|
|
||||||
InsightAPI.prototype.transactionHandler = function(txInfo) {
|
for (var i = 0; i < this.subscriptions.inv.length; i++) {
|
||||||
if(txInfo.mempool) {
|
this.subscriptions.inv[i].emit('tx', result);
|
||||||
var tx = Transaction().fromBuffer(txInfo.buffer);
|
|
||||||
tx = this.txController.transformInvTransaction(tx);
|
|
||||||
|
|
||||||
for (var i = 0; i < this.subscriptions.inv.length; i++) {
|
|
||||||
this.subscriptions.inv[i].emit('tx', tx);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -3,18 +3,20 @@
|
|||||||
var bitcore = require('bitcore-lib');
|
var bitcore = require('bitcore-lib');
|
||||||
var _ = bitcore.deps._;
|
var _ = bitcore.deps._;
|
||||||
var Message = require('bitcore-message');
|
var Message = require('bitcore-message');
|
||||||
var common = require('./common');
|
var Common = require('./common');
|
||||||
|
|
||||||
function MessagesController(node) {
|
function MessagesController(node) {
|
||||||
this.node = node;
|
this.node = node;
|
||||||
|
this.common = new Common({log: this.node.log});
|
||||||
}
|
}
|
||||||
|
|
||||||
MessagesController.prototype.verify = function(req, res) {
|
MessagesController.prototype.verify = function(req, res) {
|
||||||
|
var self = this;
|
||||||
var address = req.body.address || req.query.address;
|
var address = req.body.address || req.query.address;
|
||||||
var signature = req.body.signature || req.query.signature;
|
var signature = req.body.signature || req.query.signature;
|
||||||
var message = req.body.message || req.query.message;
|
var message = req.body.message || req.query.message;
|
||||||
if(_.isUndefined(address) || _.isUndefined(signature) || _.isUndefined(message)) {
|
if(_.isUndefined(address) || _.isUndefined(signature) || _.isUndefined(message)) {
|
||||||
return common.handleErrors({
|
return self.common.handleErrors({
|
||||||
message: 'Missing parameters (expected "address", "signature" and "message")',
|
message: 'Missing parameters (expected "address", "signature" and "message")',
|
||||||
code: 1
|
code: 1
|
||||||
}, res);
|
}, res);
|
||||||
@ -23,7 +25,7 @@ MessagesController.prototype.verify = function(req, res) {
|
|||||||
try {
|
try {
|
||||||
valid = new Message(message).verify(address, signature);
|
valid = new Message(message).verify(address, signature);
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
return common.handleErrors({
|
return self.common.handleErrors({
|
||||||
message: 'Unexpected error: ' + err.message,
|
message: 'Unexpected error: ' + err.message,
|
||||||
code: 1
|
code: 1
|
||||||
}, res);
|
}, res);
|
||||||
|
|||||||
133
lib/ratelimiter.js
Normal file
133
lib/ratelimiter.js
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var THREE_HOURS = 3 * 60 * 60 * 1000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A rate limiter to be used as an express middleware.
|
||||||
|
*
|
||||||
|
* @param {Object} options
|
||||||
|
* @param {Object} options.node - The bitcore node object
|
||||||
|
* @param {Number} options.limit - Number of requests for normal rate limiter
|
||||||
|
* @param {Number} options.interval - Interval of the normal rate limiter
|
||||||
|
* @param {Array} options.whitelist - IP addresses that should have whitelist rate limiting
|
||||||
|
* @param {Array} options.blacklist - IP addresses that should be blacklist rate limiting
|
||||||
|
* @param {Number} options.whitelistLimit - Number of requests for whitelisted clients
|
||||||
|
* @param {Number} options.whitelistInterval - Interval for whitelisted clients
|
||||||
|
* @param {Number} options.blacklistLimit - Number of requests for blacklisted clients
|
||||||
|
* @param {Number} options.blacklistInterval - Interval for blacklisted clients
|
||||||
|
*/
|
||||||
|
function RateLimiter(options) {
|
||||||
|
if (!(this instanceof RateLimiter)) {
|
||||||
|
return new RateLimiter(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!options){
|
||||||
|
options = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
this.node = options.node;
|
||||||
|
this.clients = {};
|
||||||
|
this.whitelist = options.whitelist || [];
|
||||||
|
this.blacklist = options.blacklist || [];
|
||||||
|
|
||||||
|
this.config = {
|
||||||
|
whitelist: {
|
||||||
|
totalRequests: options.whitelistLimit || 3 * 60 * 60 * 10, // 108,000
|
||||||
|
interval: options.whitelistInterval || THREE_HOURS
|
||||||
|
},
|
||||||
|
blacklist: {
|
||||||
|
totalRequests: options.blacklistLimit || 0,
|
||||||
|
interval: options.blacklistInterval || THREE_HOURS
|
||||||
|
},
|
||||||
|
normal: {
|
||||||
|
totalRequests: options.limit || 3 * 60 * 60, // 10,800
|
||||||
|
interval: options.interval || THREE_HOURS
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
RateLimiter.prototype.middleware = function() {
|
||||||
|
var self = this;
|
||||||
|
return function(req, res, next) {
|
||||||
|
self._middleware(req, res, next);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
RateLimiter.prototype._middleware = function(req, res, next) {
|
||||||
|
|
||||||
|
var name = this.getClientName(req);
|
||||||
|
var client = this.clients[name];
|
||||||
|
|
||||||
|
res.ratelimit = {
|
||||||
|
clients: this.clients,
|
||||||
|
exceeded: false
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!client) {
|
||||||
|
client = this.addClient(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.setHeader('X-RateLimit-Limit', this.config[client.type].totalRequests);
|
||||||
|
res.setHeader('X-RateLimit-Remaining', this.config[client.type].totalRequests - client.visits);
|
||||||
|
|
||||||
|
res.ratelimit.exceeded = this.exceeded(client);
|
||||||
|
res.ratelimit.client = client;
|
||||||
|
|
||||||
|
if (!this.exceeded(client)) {
|
||||||
|
client.visits++;
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
|
this.node.log.warn('Rate limited:', client);
|
||||||
|
res.status(429).jsonp({
|
||||||
|
status: 429,
|
||||||
|
error: 'Rate limit exceeded'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
RateLimiter.prototype.exceeded = function(client) {
|
||||||
|
if (this.config[client.type].totalRequests === -1) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return client.visits > this.config[client.type].totalRequests;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
RateLimiter.prototype.getClientType = function(name) {
|
||||||
|
if (this.whitelist.indexOf(name) > -1) {
|
||||||
|
return 'whitelist';
|
||||||
|
}
|
||||||
|
if (this.blacklist.indexOf(name) > -1) {
|
||||||
|
return 'blacklist';
|
||||||
|
}
|
||||||
|
return 'normal';
|
||||||
|
};
|
||||||
|
|
||||||
|
RateLimiter.prototype.getClientName = function(req) {
|
||||||
|
var name = req.headers['cf-connecting-ip'] || req.headers['x-forwarded-for'] || req.connection.remoteAddress;
|
||||||
|
return name;
|
||||||
|
};
|
||||||
|
|
||||||
|
RateLimiter.prototype.addClient = function(name) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
var client = {
|
||||||
|
name: name,
|
||||||
|
type: this.getClientType(name),
|
||||||
|
visits: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
var resetTime = this.config[client.type].interval;
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
delete self.clients[name];
|
||||||
|
}, resetTime).unref();
|
||||||
|
|
||||||
|
this.clients[name] = client;
|
||||||
|
|
||||||
|
return client;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = RateLimiter;
|
||||||
142
lib/status.js
142
lib/status.js
@ -1,78 +1,132 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
var Common = require('./common');
|
||||||
|
|
||||||
function StatusController(node) {
|
function StatusController(node) {
|
||||||
this.node = node;
|
this.node = node;
|
||||||
|
this.common = new Common({log: this.node.log});
|
||||||
}
|
}
|
||||||
|
|
||||||
StatusController.prototype.show = function(req, res) {
|
StatusController.prototype.show = function(req, res) {
|
||||||
|
var self = this;
|
||||||
var option = req.query.q;
|
var option = req.query.q;
|
||||||
|
|
||||||
switch(option) {
|
switch(option) {
|
||||||
case 'getDifficulty':
|
case 'getDifficulty':
|
||||||
res.jsonp(this.getDifficulty());
|
this.getDifficulty(function(err, result) {
|
||||||
break;
|
if (err) {
|
||||||
case 'getLastBlockHash':
|
return self.common.handleErrors(err, res);
|
||||||
res.jsonp(this.getLastBlockHash());
|
}
|
||||||
break;
|
res.jsonp(result);
|
||||||
case 'getBestBlockHash':
|
});
|
||||||
res.jsonp(this.getBestBlockHash());
|
break;
|
||||||
break;
|
case 'getLastBlockHash':
|
||||||
case 'getInfo':
|
res.jsonp(this.getLastBlockHash());
|
||||||
default:
|
break;
|
||||||
res.jsonp(this.getInfo());
|
case 'getBestBlockHash':
|
||||||
|
this.getBestBlockHash(function(err, result) {
|
||||||
|
if (err) {
|
||||||
|
return self.common.handleErrors(err, res);
|
||||||
|
}
|
||||||
|
res.jsonp(result);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'getInfo':
|
||||||
|
default:
|
||||||
|
this.getInfo(function(err, result) {
|
||||||
|
if (err) {
|
||||||
|
return self.common.handleErrors(err, res);
|
||||||
|
}
|
||||||
|
res.jsonp({
|
||||||
|
info: result
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
StatusController.prototype.getInfo = function() {
|
StatusController.prototype.getInfo = function(callback) {
|
||||||
var info = this.node.services.bitcoind.getInfo();
|
this.node.services.bitcoind.getInfo(function(err, result) {
|
||||||
return {
|
if (err) {
|
||||||
info: info
|
return callback(err);
|
||||||
};
|
}
|
||||||
|
var info = {
|
||||||
|
version: result.version,
|
||||||
|
protocolversion: result.protocolVersion,
|
||||||
|
blocks: result.blocks,
|
||||||
|
timeoffset: result.timeOffset,
|
||||||
|
connections: result.connections,
|
||||||
|
proxy: result.proxy,
|
||||||
|
difficulty: result.difficulty,
|
||||||
|
testnet: result.testnet,
|
||||||
|
relayfee: result.relayFee,
|
||||||
|
errors: result.errors,
|
||||||
|
network: result.network
|
||||||
|
};
|
||||||
|
callback(null, info);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
StatusController.prototype.getLastBlockHash = function() {
|
StatusController.prototype.getLastBlockHash = function() {
|
||||||
var hash = this.node.services.db.tip.hash;
|
var hash = this.node.services.bitcoind.tiphash;
|
||||||
return {
|
return {
|
||||||
syncTipHash: hash,
|
syncTipHash: hash,
|
||||||
lastblockhash: hash
|
lastblockhash: hash
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
StatusController.prototype.getBestBlockHash = function() {
|
StatusController.prototype.getBestBlockHash = function(callback) {
|
||||||
var hash = this.node.services.bitcoind.getBestBlockHash();
|
this.node.services.bitcoind.getBestBlockHash(function(err, hash) {
|
||||||
return {
|
if (err) {
|
||||||
bestblockhash: hash
|
return callback(err);
|
||||||
};
|
}
|
||||||
|
callback(null, {
|
||||||
|
bestblockhash: hash
|
||||||
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
StatusController.prototype.getDifficulty = function() {
|
StatusController.prototype.getDifficulty = function(callback) {
|
||||||
var info = this.node.services.bitcoind.getInfo();
|
this.node.services.bitcoind.getInfo(function(err, info) {
|
||||||
return {
|
if (err) {
|
||||||
difficulty: info.difficulty
|
return callback(err);
|
||||||
};
|
}
|
||||||
|
callback(null, {
|
||||||
|
difficulty: info.difficulty
|
||||||
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
StatusController.prototype.sync = function(req, res) {
|
StatusController.prototype.sync = function(req, res) {
|
||||||
|
var self = this;
|
||||||
var status = 'syncing';
|
var status = 'syncing';
|
||||||
if(this.node.services.bitcoind.isSynced() && this.node.services.db.tip.__height === this.node.services.bitcoind.height) {
|
|
||||||
status = 'finished';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not exactly the total blockchain height,
|
this.node.services.bitcoind.isSynced(function(err, synced) {
|
||||||
// but we will reach 100% when our db and bitcoind are both fully synced
|
if (err) {
|
||||||
var totalHeight = this.node.services.bitcoind.height / (this.node.services.bitcoind.syncPercentage() / 100);
|
return self.common.handleErrors(err, res);
|
||||||
|
}
|
||||||
|
if (synced) {
|
||||||
|
status = 'finished';
|
||||||
|
}
|
||||||
|
|
||||||
var info = {
|
self.node.services.bitcoind.syncPercentage(function(err, percentage) {
|
||||||
status: status,
|
if (err) {
|
||||||
blockChainHeight: this.node.services.bitcoind.height,
|
return self.common.handleErrors(err, res);
|
||||||
syncPercentage: Math.round(this.node.services.db.tip.__height / totalHeight * 100),
|
}
|
||||||
height: this.node.services.db.tip.__height,
|
var info = {
|
||||||
error: null,
|
status: status,
|
||||||
type: 'bitcore node'
|
blockChainHeight: self.node.services.bitcoind.height,
|
||||||
};
|
syncPercentage: Math.round(percentage),
|
||||||
|
height: self.node.services.bitcoind.height,
|
||||||
|
error: null,
|
||||||
|
type: 'bitcore node'
|
||||||
|
};
|
||||||
|
|
||||||
|
res.jsonp(info);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
res.jsonp(info);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Hard coded to make insight ui happy, but not applicable
|
// Hard coded to make insight ui happy, but not applicable
|
||||||
|
|||||||
@ -3,13 +3,14 @@
|
|||||||
var bitcore = require('bitcore-lib');
|
var bitcore = require('bitcore-lib');
|
||||||
var _ = bitcore.deps._;
|
var _ = bitcore.deps._;
|
||||||
var $ = bitcore.util.preconditions;
|
var $ = bitcore.util.preconditions;
|
||||||
var common = require('./common');
|
var Common = require('./common');
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
|
|
||||||
var MAXINT = 0xffffffff; // Math.pow(2, 32) - 1;
|
var MAXINT = 0xffffffff; // Math.pow(2, 32) - 1;
|
||||||
|
|
||||||
function TxController(node) {
|
function TxController(node) {
|
||||||
this.node = node;
|
this.node = node;
|
||||||
|
this.common = new Common({log: this.node.log});
|
||||||
}
|
}
|
||||||
|
|
||||||
TxController.prototype.show = function(req, res) {
|
TxController.prototype.show = function(req, res) {
|
||||||
@ -21,179 +22,125 @@ TxController.prototype.show = function(req, res) {
|
|||||||
/**
|
/**
|
||||||
* Find transaction by hash ...
|
* Find transaction by hash ...
|
||||||
*/
|
*/
|
||||||
TxController.prototype.transaction = function(req, res, next, txid) {
|
TxController.prototype.transaction = function(req, res, next) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
var txid = req.params.txid;
|
||||||
|
|
||||||
this.node.getTransactionWithBlockInfo(txid, true, function(err, transaction) {
|
this.node.getDetailedTransaction(txid, function(err, transaction) {
|
||||||
if (err && err instanceof self.node.errors.Transaction.NotFound) {
|
if (err && err.code === -5) {
|
||||||
return common.handleErrors(null, res);
|
return self.common.handleErrors(null, res);
|
||||||
} else if(err) {
|
} else if(err) {
|
||||||
return common.handleErrors(err, res);
|
return self.common.handleErrors(err, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
transaction.populateInputs(self.node.services.db, [], function(err) {
|
self.transformTransaction(transaction, function(err, transformedTransaction) {
|
||||||
if(err) {
|
if (err) {
|
||||||
return res.send({
|
return self.common.handleErrors(err, res);
|
||||||
error: err.toString()
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
req.transaction = transformedTransaction;
|
||||||
self.transformTransaction(transaction, function(err, transformedTransaction) {
|
next();
|
||||||
if (err) {
|
|
||||||
return common.handleErrors(err, res);
|
|
||||||
}
|
|
||||||
req.transaction = transformedTransaction;
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
TxController.prototype.transformTransaction = function(transaction, callback) {
|
TxController.prototype.transformTransaction = function(transaction, callback) {
|
||||||
$.checkArgument(_.isFunction(callback));
|
$.checkArgument(_.isFunction(callback));
|
||||||
var self = this;
|
|
||||||
var txid = transaction.id;
|
|
||||||
var txObj = transaction.toObject();
|
|
||||||
|
|
||||||
var confirmations = 0;
|
var confirmations = 0;
|
||||||
if(transaction.__height >= 0) {
|
if(transaction.height >= 0) {
|
||||||
confirmations = this.node.services.db.tip.__height - transaction.__height + 1;
|
confirmations = this.node.services.bitcoind.height - transaction.height + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
var transformed = {
|
var transformed = {
|
||||||
txid: txObj.hash,
|
txid: transaction.hash,
|
||||||
version: txObj.version,
|
version: transaction.version,
|
||||||
locktime: txObj.nLockTime
|
locktime: transaction.locktime
|
||||||
};
|
};
|
||||||
|
|
||||||
if(transaction.isCoinbase()) {
|
if(transaction.coinbase) {
|
||||||
transformed.vin = [
|
transformed.vin = [
|
||||||
{
|
{
|
||||||
coinbase: txObj.inputs[0].script,
|
coinbase: transaction.inputs[0].script,
|
||||||
sequence: txObj.inputs[0].sequenceNumber,
|
sequence: transaction.inputs[0].sequence,
|
||||||
n: 0
|
n: 0
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
transformed.vin = txObj.inputs.map(this.transformInput.bind(this));
|
transformed.vin = transaction.inputs.map(this.transformInput.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
async.map(
|
transformed.vout = transaction.outputs.map(this.transformOutput.bind(this));
|
||||||
Object.keys(txObj.outputs),
|
|
||||||
function(outputIndex, next) {
|
|
||||||
outputIndex = parseInt(outputIndex);
|
|
||||||
var output = txObj.outputs[outputIndex];
|
|
||||||
self.transformOutput(txid, output, outputIndex, next);
|
|
||||||
},
|
|
||||||
function(err, vout) {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
transformed.vout = vout;
|
transformed.blockhash = transaction.blockHash;
|
||||||
|
transformed.blockheight = transaction.height;
|
||||||
|
transformed.confirmations = confirmations;
|
||||||
|
// TODO consider mempool txs with receivedTime?
|
||||||
|
var time = transaction.blockTimestamp ? transaction.blockTimestamp : Math.round(Date.now() / 1000);
|
||||||
|
transformed.time = time;
|
||||||
|
if (transformed.confirmations) {
|
||||||
|
transformed.blocktime = transformed.time;
|
||||||
|
}
|
||||||
|
|
||||||
transformed.blockhash = transaction.__blockHash;
|
if(transaction.coinbase) {
|
||||||
transformed.blockheight = transaction.__height;
|
transformed.isCoinBase = true;
|
||||||
transformed.confirmations = confirmations;
|
}
|
||||||
var time = transaction.__timestamp ? transaction.__timestamp : Math.round(Date.now() / 1000);
|
|
||||||
transformed.time = time;
|
|
||||||
if (transformed.confirmations) {
|
|
||||||
transformed.blocktime = transformed.time;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(transaction.isCoinbase()) {
|
transformed.valueOut = transaction.outputSatoshis / 1e8;
|
||||||
transformed.isCoinBase = true;
|
transformed.size = transaction.hex.length / 2; // in bytes
|
||||||
}
|
if (!transaction.coinbase) {
|
||||||
|
transformed.valueIn = transaction.inputSatoshis / 1e8;
|
||||||
transformed.valueOut = transaction.outputAmount / 1e8;
|
transformed.fees = transaction.feeSatoshis / 1e8;
|
||||||
transformed.size = transaction.toBuffer().length;
|
}
|
||||||
if(transaction.hasAllUtxoInfo()) {
|
|
||||||
transformed.valueIn = transaction.inputAmount / 1e8;
|
|
||||||
transformed.fees = transaction.getFee() / 1e8;
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(null, transformed);
|
|
||||||
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
|
callback(null, transformed);
|
||||||
};
|
};
|
||||||
|
|
||||||
TxController.prototype.transformInput = function(input, index) {
|
TxController.prototype.transformInput = function(input, index) {
|
||||||
// Input scripts are validated and can be assumed to be valid
|
// Input scripts are validated and can be assumed to be valid
|
||||||
var script = new bitcore.Script(input.script);
|
|
||||||
var transformed = {
|
var transformed = {
|
||||||
txid: input.prevTxId,
|
txid: input.prevTxId,
|
||||||
vout: input.outputIndex,
|
vout: input.outputIndex,
|
||||||
scriptSig: {
|
scriptSig: {
|
||||||
asm: script.toASM(),
|
asm: input.scriptAsm,
|
||||||
hex: input.script
|
hex: input.script
|
||||||
},
|
},
|
||||||
sequence: input.sequenceNumber,
|
sequence: input.sequence,
|
||||||
n: index
|
n: index
|
||||||
};
|
};
|
||||||
|
|
||||||
if(input.output) {
|
transformed.addr = input.address;
|
||||||
transformed.addr = bitcore.Script(input.output.script).toAddress(this.node.network).toString();
|
transformed.valueSat = input.satoshis;
|
||||||
transformed.valueSat = input.output.satoshis;
|
transformed.value = input.satoshis / 1e8;
|
||||||
transformed.value = input.output.satoshis / 1e8;
|
transformed.doubleSpentTxID = null; // TODO
|
||||||
transformed.doubleSpentTxID = null; // TODO
|
//transformed.isConfirmed = null; // TODO
|
||||||
//transformed.isConfirmed = null; // TODO
|
//transformed.confirmations = null; // TODO
|
||||||
//transformed.confirmations = null; // TODO
|
//transformed.unconfirmedInput = null; // TODO
|
||||||
//transformed.unconfirmedInput = null; // TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
return transformed;
|
return transformed;
|
||||||
};
|
};
|
||||||
|
|
||||||
TxController.prototype.transformOutput = function(txid, output, index, callback) {
|
TxController.prototype.transformOutput = function(output, index) {
|
||||||
var self = this;
|
|
||||||
var transformed = {
|
var transformed = {
|
||||||
value: (output.satoshis / 1e8).toFixed(8),
|
value: (output.satoshis / 1e8).toFixed(8),
|
||||||
n: index,
|
n: index,
|
||||||
scriptPubKey: {
|
scriptPubKey: {
|
||||||
hex: output.script,
|
hex: output.script,
|
||||||
|
asm: output.scriptAsm
|
||||||
//reqSigs: null, // TODO
|
//reqSigs: null, // TODO
|
||||||
}
|
},
|
||||||
|
spentTxId: output.spentTxId || null,
|
||||||
|
spentIndex: _.isUndefined(output.spentIndex) ? null : output.spentIndex,
|
||||||
|
spentHeight: output.spentHeight || null
|
||||||
//spentTs: undefined // TODO
|
//spentTs: undefined // TODO
|
||||||
};
|
};
|
||||||
|
|
||||||
var script;
|
if (output.address) {
|
||||||
try {
|
transformed.scriptPubKey.addresses = [output.address];
|
||||||
// Output scripts can be invalid, so we need to try/catch
|
var address = bitcore.Address(output.address); //TODO return type from bitcore-node
|
||||||
script = new bitcore.Script(output.script);
|
transformed.scriptPubKey.type = address.type;
|
||||||
} catch (err) {
|
|
||||||
script = false;
|
|
||||||
}
|
}
|
||||||
if (script) {
|
return transformed;
|
||||||
transformed.scriptPubKey.asm = script.toASM();
|
|
||||||
var address = script.toAddress(this.node.network);
|
|
||||||
if (address) {
|
|
||||||
transformed.scriptPubKey.addresses = [address.toString()];
|
|
||||||
transformed.scriptPubKey.type = address.type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var options = {
|
|
||||||
queryMempool: true
|
|
||||||
};
|
|
||||||
|
|
||||||
self.node.services.address.getInputForOutput(
|
|
||||||
txid,
|
|
||||||
index,
|
|
||||||
options,
|
|
||||||
function(err, inputResult) {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
if (inputResult) {
|
|
||||||
transformed.spentTxId = inputResult.inputTxId;
|
|
||||||
transformed.spentIndex = inputResult.inputIndex;
|
|
||||||
}
|
|
||||||
callback(null, transformed);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
TxController.prototype.transformInvTransaction = function(transaction) {
|
TxController.prototype.transformInvTransaction = function(transaction) {
|
||||||
@ -228,14 +175,15 @@ TxController.prototype.transformInvTransaction = function(transaction) {
|
|||||||
return transformed;
|
return transformed;
|
||||||
};
|
};
|
||||||
|
|
||||||
TxController.prototype.rawTransaction = function(req, res, next, txid) {
|
TxController.prototype.rawTransaction = function(req, res, next) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
var txid = req.params.txid;
|
||||||
|
|
||||||
this.node.getTransaction(txid, true, function(err, transaction) {
|
this.node.getTransaction(txid, function(err, transaction) {
|
||||||
if (err && err instanceof self.node.errors.Transaction.NotFound) {
|
if (err && err.code === -5) {
|
||||||
return common.handleErrors(null, res);
|
return self.common.handleErrors(null, res);
|
||||||
} else if(err) {
|
} else if(err) {
|
||||||
return common.handleErrors(err, res);
|
return self.common.handleErrors(err, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
req.rawTransaction = {
|
req.rawTransaction = {
|
||||||
@ -262,36 +210,34 @@ TxController.prototype.list = function(req, res) {
|
|||||||
var pagesTotal = 1;
|
var pagesTotal = 1;
|
||||||
|
|
||||||
if(blockHash) {
|
if(blockHash) {
|
||||||
self.node.getBlock(blockHash, function(err, block) {
|
self.node.getBlockOverview(blockHash, function(err, block) {
|
||||||
if(err && err.message === 'Block not found.') {
|
if(err && err.code === -5) {
|
||||||
return common.handleErrors(null, res);
|
return self.common.handleErrors(null, res);
|
||||||
} else if(err) {
|
} else if(err) {
|
||||||
return common.handleErrors(err, res);
|
return self.common.handleErrors(err, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
var blockInfo = self.node.services.bitcoind.getBlockIndex(block.hash);
|
var totalTxs = block.txids.length;
|
||||||
var txs = block.transactions;
|
var txids;
|
||||||
var totalTxs = txs.length;
|
|
||||||
|
|
||||||
if(!_.isUndefined(page)) {
|
if(!_.isUndefined(page)) {
|
||||||
txs = txs.splice(page * pageLength, pageLength);
|
var start = page * pageLength;
|
||||||
|
txids = block.txids.slice(start, start + pageLength);
|
||||||
pagesTotal = Math.ceil(totalTxs / pageLength);
|
pagesTotal = Math.ceil(totalTxs / pageLength);
|
||||||
|
} else {
|
||||||
|
txids = block.txids;
|
||||||
}
|
}
|
||||||
|
|
||||||
async.mapSeries(txs, function(tx, next) {
|
async.mapSeries(txids, function(txid, next) {
|
||||||
tx.__blockHash = block.hash;
|
self.node.getDetailedTransaction(txid, function(err, transaction) {
|
||||||
tx.__height = blockInfo.height;
|
if (err) {
|
||||||
tx.__timestamp = block.header.time;
|
|
||||||
|
|
||||||
tx.populateInputs(self.node.services.db, [], function(err) {
|
|
||||||
if(err) {
|
|
||||||
return next(err);
|
return next(err);
|
||||||
}
|
}
|
||||||
self.transformTransaction(tx, next);
|
self.transformTransaction(transaction, next);
|
||||||
});
|
});
|
||||||
}, function(err, transformed) {
|
}, function(err, transformed) {
|
||||||
if(err) {
|
if(err) {
|
||||||
return common.handleErrors(err, res);
|
return self.common.handleErrors(err, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
res.jsonp({
|
res.jsonp({
|
||||||
@ -299,6 +245,7 @@ TxController.prototype.list = function(req, res) {
|
|||||||
txs: transformed
|
txs: transformed
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
} else if(address) {
|
} else if(address) {
|
||||||
var options = {
|
var options = {
|
||||||
@ -308,7 +255,7 @@ TxController.prototype.list = function(req, res) {
|
|||||||
|
|
||||||
self.node.getAddressHistory(address, options, function(err, result) {
|
self.node.getAddressHistory(address, options, function(err, result) {
|
||||||
if(err) {
|
if(err) {
|
||||||
return common.handleErrors(err, res);
|
return self.common.handleErrors(err, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
var txs = result.items.map(function(info) {
|
var txs = result.items.map(function(info) {
|
||||||
@ -324,7 +271,7 @@ TxController.prototype.list = function(req, res) {
|
|||||||
},
|
},
|
||||||
function(err, transformed) {
|
function(err, transformed) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return common.handleErrors(err, res);
|
return self.common.handleErrors(err, res);
|
||||||
}
|
}
|
||||||
res.jsonp({
|
res.jsonp({
|
||||||
pagesTotal: Math.ceil(result.totalCount / pageLength),
|
pagesTotal: Math.ceil(result.totalCount / pageLength),
|
||||||
@ -334,15 +281,16 @@ TxController.prototype.list = function(req, res) {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return common.handleErrors(new Error('Block hash or address expected'), res);
|
return self.common.handleErrors(new Error('Block hash or address expected'), res);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
TxController.prototype.send = function(req, res) {
|
TxController.prototype.send = function(req, res) {
|
||||||
|
var self = this;
|
||||||
this.node.sendTransaction(req.body.rawtx, function(err, txid) {
|
this.node.sendTransaction(req.body.rawtx, function(err, txid) {
|
||||||
if(err) {
|
if(err) {
|
||||||
// TODO handle specific errors
|
// TODO handle specific errors
|
||||||
return common.handleErrors(err, res);
|
return self.common.handleErrors(err, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json({'txid': txid});
|
res.json({'txid': txid});
|
||||||
|
|||||||
20
lib/utils.js
20
lib/utils.js
@ -1,8 +1,12 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
|
var async = require('async');
|
||||||
|
var Common = require('./common');
|
||||||
|
|
||||||
function UtilsController(node) {
|
function UtilsController(node) {
|
||||||
this.node = node;
|
this.node = node;
|
||||||
|
this.common = new Common({log: this.node.log});
|
||||||
}
|
}
|
||||||
|
|
||||||
UtilsController.prototype.estimateFee = function(req, res) {
|
UtilsController.prototype.estimateFee = function(req, res) {
|
||||||
@ -10,14 +14,22 @@ UtilsController.prototype.estimateFee = function(req, res) {
|
|||||||
var args = req.query.nbBlocks || '2';
|
var args = req.query.nbBlocks || '2';
|
||||||
var nbBlocks = args.split(',');
|
var nbBlocks = args.split(',');
|
||||||
|
|
||||||
var result = nbBlocks.map(function(n) {
|
async.map(nbBlocks, function(n, next) {
|
||||||
var num = parseInt(n);
|
var num = parseInt(n);
|
||||||
// Insight and Bitcoin JSON-RPC return bitcoin for this value (instead of satoshis).
|
// Insight and Bitcoin JSON-RPC return bitcoin for this value (instead of satoshis).
|
||||||
var fee = self.node.services.bitcoind.estimateFee(num) / 1e8;
|
self.node.services.bitcoind.estimateFee(num, function(err, fee) {
|
||||||
return [num, fee];
|
if (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
next(null, [num, fee]);
|
||||||
|
});
|
||||||
|
}, function(err, result) {
|
||||||
|
if (err) {
|
||||||
|
return self.common.handleErrors(err, res);
|
||||||
|
}
|
||||||
|
res.jsonp(_.zipObject(result));
|
||||||
});
|
});
|
||||||
|
|
||||||
res.jsonp(_.zipObject(result));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = UtilsController;
|
module.exports = UtilsController;
|
||||||
|
|||||||
@ -64,14 +64,17 @@
|
|||||||
"bitcore-lib": "^0.13.7",
|
"bitcore-lib": "^0.13.7",
|
||||||
"bitcore-message": "^1.0.1",
|
"bitcore-message": "^1.0.1",
|
||||||
"body-parser": "^1.13.3",
|
"body-parser": "^1.13.3",
|
||||||
|
"compression": "^1.6.1",
|
||||||
"lodash": "^2.4.1",
|
"lodash": "^2.4.1",
|
||||||
|
"lru-cache": "^4.0.1",
|
||||||
|
"morgan": "^1.7.0",
|
||||||
"request": "^2.64.0"
|
"request": "^2.64.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"chai": "*",
|
"chai": "^3.5.0",
|
||||||
"mocha": "~1.16.2",
|
"mocha": "^2.4.5",
|
||||||
"proxyquire": "^1.7.2",
|
"proxyquire": "^1.7.2",
|
||||||
"should": "^2.1.1",
|
"should": "^8.3.1",
|
||||||
"sinon": "^1.10.3"
|
"sinon": "^1.10.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,135 +9,138 @@ var txinfos = {
|
|||||||
totalCount: 2,
|
totalCount: 2,
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
"address": "mkPvAKZ2rar6qeG3KjBtJHHMSP1wFZH7Er",
|
'address': 'mkPvAKZ2rar6qeG3KjBtJHHMSP1wFZH7Er',
|
||||||
"satoshis": 2782729129,
|
'satoshis': 2782729129,
|
||||||
"height": 534105,
|
'height': 534105,
|
||||||
"confirmations": 123,
|
'confirmations': 123,
|
||||||
"timestamp": 1441068774,
|
'timestamp': 1441068774,
|
||||||
"fees": 35436,
|
'fees': 35436,
|
||||||
"outputIndexes": [
|
'outputIndexes': [
|
||||||
0
|
0
|
||||||
],
|
],
|
||||||
"inputIndexes": [],
|
'inputIndexes': [],
|
||||||
"tx": {
|
'tx': {
|
||||||
"hash": "bb0ec3b96209fac9529570ea6f83a86af2cceedde4aaf2bfcc4796680d23f1c7",
|
'hash': 'bb0ec3b96209fac9529570ea6f83a86af2cceedde4aaf2bfcc4796680d23f1c7',
|
||||||
"version": 1,
|
'version': 1,
|
||||||
"inputs": [
|
'inputs': [
|
||||||
{
|
{
|
||||||
"prevTxId": "ea5e5bafbf29cdf6f6097ab344128477e67889d4d6203cb43594836daa6cc425",
|
'prevTxId': 'ea5e5bafbf29cdf6f6097ab344128477e67889d4d6203cb43594836daa6cc425',
|
||||||
"outputIndex": 1,
|
'outputIndex': 1,
|
||||||
"sequenceNumber": 4294967294,
|
'sequenceNumber': 4294967294,
|
||||||
"script": "483045022100f4d169783bef70e3943d2a617cce55d9fe4e33fc6f9880b8277265e2f619a97002201238648abcdf52960500664e969046d41755f7fc371971ebc78002fc418465a6012103acdcd31d51272403ce0829447e59e2ac9e08ed0bf92011cbf7420addf24534e6",
|
'script': '483045022100f4d169783bef70e3943d2a617cce55d9fe4e33fc6f9880b8277265e2f619a97002201238648abcdf52960500664e969046d41755f7fc371971ebc78002fc418465a6012103acdcd31d51272403ce0829447e59e2ac9e08ed0bf92011cbf7420addf24534e6',
|
||||||
"scriptString": "72 0x3045022100f4d169783bef70e3943d2a617cce55d9fe4e33fc6f9880b8277265e2f619a97002201238648abcdf52960500664e969046d41755f7fc371971ebc78002fc418465a601 33 0x03acdcd31d51272403ce0829447e59e2ac9e08ed0bf92011cbf7420addf24534e6",
|
'scriptString': '72 0x3045022100f4d169783bef70e3943d2a617cce55d9fe4e33fc6f9880b8277265e2f619a97002201238648abcdf52960500664e969046d41755f7fc371971ebc78002fc418465a601 33 0x03acdcd31d51272403ce0829447e59e2ac9e08ed0bf92011cbf7420addf24534e6',
|
||||||
"output": {
|
'output': {
|
||||||
"satoshis": 2796764565,
|
'satoshis': 2796764565,
|
||||||
"script": "76a91488b1fe8aec5ae4358a11447a2f22b2781faedb9b88ac"
|
'script': '76a91488b1fe8aec5ae4358a11447a2f22b2781faedb9b88ac'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"outputs": [
|
'outputs': [
|
||||||
{
|
{
|
||||||
"satoshis": 2782729129,
|
'satoshis': 2782729129,
|
||||||
"script": "76a9143583efb5e64a4668c6c54bb5fcc30af4417b4f2d88ac"
|
'script': '76a9143583efb5e64a4668c6c54bb5fcc30af4417b4f2d88ac'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"satoshis": 14000000,
|
'satoshis': 14000000,
|
||||||
"script": "76a9149713201957f42379e574d7c70d506ee49c2c8ad688ac"
|
'script': '76a9149713201957f42379e574d7c70d506ee49c2c8ad688ac'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"nLockTime": 534089
|
'nLockTime': 534089
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"address": "mkPvAKZ2rar6qeG3KjBtJHHMSP1wFZH7Er",
|
'address': 'mkPvAKZ2rar6qeG3KjBtJHHMSP1wFZH7Er',
|
||||||
"satoshis": -2782729129,
|
'satoshis': -2782729129,
|
||||||
"height": 534110,
|
'height': 534110,
|
||||||
"confirmations": 118,
|
'confirmations': 118,
|
||||||
"timestamp": 1441072817,
|
'timestamp': 1441072817,
|
||||||
"fees": 35437,
|
'fees': 35437,
|
||||||
"outputIndexes": [],
|
'outputIndexes': [],
|
||||||
"inputIndexes": [
|
'inputIndexes': [
|
||||||
"0"
|
'0'
|
||||||
],
|
],
|
||||||
"tx": {
|
'tx': {
|
||||||
"hash": "01f700df84c466f2a389440e5eeacdc47d04f380c39e5d19dce2ce91a11ecba3",
|
'hash': '01f700df84c466f2a389440e5eeacdc47d04f380c39e5d19dce2ce91a11ecba3',
|
||||||
"version": 1,
|
'version': 1,
|
||||||
"inputs": [
|
'inputs': [
|
||||||
{
|
{
|
||||||
"prevTxId": "bb0ec3b96209fac9529570ea6f83a86af2cceedde4aaf2bfcc4796680d23f1c7",
|
'prevTxId': 'bb0ec3b96209fac9529570ea6f83a86af2cceedde4aaf2bfcc4796680d23f1c7',
|
||||||
"outputIndex": 0,
|
'outputIndex': 0,
|
||||||
"sequenceNumber": 4294967294,
|
'sequenceNumber': 4294967294,
|
||||||
"script": "47304402201ee69281db6b95bb1aa3074059b67581635b719e8f64e4c2694db6ec56ad9447022011e91528996ea459b1fb2c0b59363fecbefe4bc2ca90f7b2382bdaa358f2d5640121034cc057b12a68ee79df998004b9a1341bbb18b17ea4939bebaa3bac001e940f24",
|
'script': '47304402201ee69281db6b95bb1aa3074059b67581635b719e8f64e4c2694db6ec56ad9447022011e91528996ea459b1fb2c0b59363fecbefe4bc2ca90f7b2382bdaa358f2d5640121034cc057b12a68ee79df998004b9a1341bbb18b17ea4939bebaa3bac001e940f24',
|
||||||
"scriptString": "71 0x304402201ee69281db6b95bb1aa3074059b67581635b719e8f64e4c2694db6ec56ad9447022011e91528996ea459b1fb2c0b59363fecbefe4bc2ca90f7b2382bdaa358f2d56401 33 0x034cc057b12a68ee79df998004b9a1341bbb18b17ea4939bebaa3bac001e940f24",
|
'scriptString': '71 0x304402201ee69281db6b95bb1aa3074059b67581635b719e8f64e4c2694db6ec56ad9447022011e91528996ea459b1fb2c0b59363fecbefe4bc2ca90f7b2382bdaa358f2d56401 33 0x034cc057b12a68ee79df998004b9a1341bbb18b17ea4939bebaa3bac001e940f24',
|
||||||
"output": {
|
'output': {
|
||||||
"satoshis": 2782729129,
|
'satoshis': 2782729129,
|
||||||
"script": "76a9143583efb5e64a4668c6c54bb5fcc30af4417b4f2d88ac"
|
'script': '76a9143583efb5e64a4668c6c54bb5fcc30af4417b4f2d88ac'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"outputs": [
|
'outputs': [
|
||||||
{
|
{
|
||||||
"satoshis": 2764693692,
|
'satoshis': 2764693692,
|
||||||
"script": "76a91456e446bc3489543d8324c6d0271524c0bd0506dd88ac"
|
'script': '76a91456e446bc3489543d8324c6d0271524c0bd0506dd88ac'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"satoshis": 18000000,
|
'satoshis': 18000000,
|
||||||
"script": "76a914011d2963b619186a318f768dddfd98cd553912a088ac"
|
'script': '76a914011d2963b619186a318f768dddfd98cd553912a088ac'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"nLockTime": 534099
|
'nLockTime': 534099
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
var tx = bitcore.Transaction().fromObject({
|
var tx = {
|
||||||
"hash": "63b68becb0e514b32317f4b29a5cf0627d4087e54ac17f686fcb1d9a27680f73",
|
height: 534181,
|
||||||
"version": 1,
|
blockTimestamp: 1441116143,
|
||||||
"inputs": [
|
blockHash: '0000000000000041ddc94ecf4f86a456a83b2e320c36c6f0c13ff92c7e75f013',
|
||||||
|
hex: '0100000002f379708395d0a0357514205a3758a0317926428356e54a09089852fc6f7297ea010000008a473044022054233934268b30be779fad874ef42e8db928ba27a1b612d5f111b3ee95eb271c022024272bbaf2dcc4050bd3b9dfa3c93884f6ba6ad7d257598b8245abb65b5ab1e40141040682fdb281a8533e21e13dfd1fcfa424912a85b6cdc4136b5842c85de05ac1f0e4a013f20702adeb53329de13b2ef388e5ed6244676f4f1ee4ee685ab607964dffffffffb758ffd4c31693d9620f326385404530a079d5e60a90b94e46d3c2dbc29c0a98020000008a473044022044938ac3f8fcb8da29011df6397ed28cc7e894cdc35d596d4f3623bd8c7e465f022014829c6e0bd7ee97a1bcfef6b85c5fd232653f289394fc6ce6ebb41c73403f1b014104d9ccf88efc6e5be3151fae5e848efd94c91d75e7bf621f9f724a8caff51415338525d3239fae6b93826edf759dd562f77693e55dfa852ffd96a92d683db590f2ffffffff03605b0300000000001976a914b9bbd76588d9e4e09f0369a9aa0b2749a11c4e8d88ac40992d03000000001976a914d2ec20bb8e5f25a52f730384b803d95683250e0b88ac256c0400000000001976a914583df9fa56ad961051e00ca93e68dfaf1eab9ec588ac00000000',
|
||||||
|
hash: '63b68becb0e514b32317f4b29a5cf0627d4087e54ac17f686fcb1d9a27680f73',
|
||||||
|
version: 1,
|
||||||
|
inputSatoshis: 53839829,
|
||||||
|
outputSatoshis: 53829829,
|
||||||
|
feeSatoshis: 10000,
|
||||||
|
inputs: [
|
||||||
{
|
{
|
||||||
"prevTxId": "ea97726ffc529808094ae5568342267931a058375a20147535a0d095837079f3",
|
address: 'moFfnRwt77pApKnnU6m5uocFaa43aAYpt5',
|
||||||
"outputIndex": 1,
|
prevTxId: 'ea97726ffc529808094ae5568342267931a058375a20147535a0d095837079f3',
|
||||||
"sequenceNumber": 4294967295,
|
outputIndex: 1,
|
||||||
"script": "473044022054233934268b30be779fad874ef42e8db928ba27a1b612d5f111b3ee95eb271c022024272bbaf2dcc4050bd3b9dfa3c93884f6ba6ad7d257598b8245abb65b5ab1e40141040682fdb281a8533e21e13dfd1fcfa424912a85b6cdc4136b5842c85de05ac1f0e4a013f20702adeb53329de13b2ef388e5ed6244676f4f1ee4ee685ab607964d",
|
sequence: 4294967295,
|
||||||
"scriptString": "71 0x3044022054233934268b30be779fad874ef42e8db928ba27a1b612d5f111b3ee95eb271c022024272bbaf2dcc4050bd3b9dfa3c93884f6ba6ad7d257598b8245abb65b5ab1e401 65 0x040682fdb281a8533e21e13dfd1fcfa424912a85b6cdc4136b5842c85de05ac1f0e4a013f20702adeb53329de13b2ef388e5ed6244676f4f1ee4ee685ab607964d",
|
script: '473044022054233934268b30be779fad874ef42e8db928ba27a1b612d5f111b3ee95eb271c022024272bbaf2dcc4050bd3b9dfa3c93884f6ba6ad7d257598b8245abb65b5ab1e40141040682fdb281a8533e21e13dfd1fcfa424912a85b6cdc4136b5842c85de05ac1f0e4a013f20702adeb53329de13b2ef388e5ed6244676f4f1ee4ee685ab607964d',
|
||||||
"output": {
|
scriptAsm: '71 0x3044022054233934268b30be779fad874ef42e8db928ba27a1b612d5f111b3ee95eb271c022024272bbaf2dcc4050bd3b9dfa3c93884f6ba6ad7d257598b8245abb65b5ab1e401 65 0x040682fdb281a8533e21e13dfd1fcfa424912a85b6cdc4136b5842c85de05ac1f0e4a013f20702adeb53329de13b2ef388e5ed6244676f4f1ee4ee685ab607964d',
|
||||||
"satoshis": 53540000,
|
satoshis: 53540000,
|
||||||
"script": "76a91454dcfbff9e109bf369e457f6b0f869f4e647076488ac"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"prevTxId": "980a9cc2dbc2d3464eb9900ae6d579a03045408563320f62d99316c3d4ff58b7",
|
address: 'n1XJBAyU4hNR4xRtY3UxnmAteoJX83p5qv',
|
||||||
"outputIndex": 2,
|
prevTxId: '980a9cc2dbc2d3464eb9900ae6d579a03045408563320f62d99316c3d4ff58b7',
|
||||||
"sequenceNumber": 4294967295,
|
outputIndex: 2,
|
||||||
"script": "473044022044938ac3f8fcb8da29011df6397ed28cc7e894cdc35d596d4f3623bd8c7e465f022014829c6e0bd7ee97a1bcfef6b85c5fd232653f289394fc6ce6ebb41c73403f1b014104d9ccf88efc6e5be3151fae5e848efd94c91d75e7bf621f9f724a8caff51415338525d3239fae6b93826edf759dd562f77693e55dfa852ffd96a92d683db590f2",
|
sequence: 4294967295,
|
||||||
"scriptString": "71 0x3044022044938ac3f8fcb8da29011df6397ed28cc7e894cdc35d596d4f3623bd8c7e465f022014829c6e0bd7ee97a1bcfef6b85c5fd232653f289394fc6ce6ebb41c73403f1b01 65 0x04d9ccf88efc6e5be3151fae5e848efd94c91d75e7bf621f9f724a8caff51415338525d3239fae6b93826edf759dd562f77693e55dfa852ffd96a92d683db590f2",
|
script: '473044022044938ac3f8fcb8da29011df6397ed28cc7e894cdc35d596d4f3623bd8c7e465f022014829c6e0bd7ee97a1bcfef6b85c5fd232653f289394fc6ce6ebb41c73403f1b014104d9ccf88efc6e5be3151fae5e848efd94c91d75e7bf621f9f724a8caff51415338525d3239fae6b93826edf759dd562f77693e55dfa852ffd96a92d683db590f2',
|
||||||
"output": {
|
scriptAsm: '71 0x3044022044938ac3f8fcb8da29011df6397ed28cc7e894cdc35d596d4f3623bd8c7e465f022014829c6e0bd7ee97a1bcfef6b85c5fd232653f289394fc6ce6ebb41c73403f1b01 65 0x04d9ccf88efc6e5be3151fae5e848efd94c91d75e7bf621f9f724a8caff51415338525d3239fae6b93826edf759dd562f77693e55dfa852ffd96a92d683db590f2',
|
||||||
"satoshis": 299829,
|
satoshis: 299829,
|
||||||
"script": "76a914db731c9ebf3874d75ee26b9c19b692d278c283f788ac"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"outputs": [
|
outputs: [
|
||||||
{
|
{
|
||||||
"satoshis": 220000,
|
satoshis: 220000,
|
||||||
"script": "76a914b9bbd76588d9e4e09f0369a9aa0b2749a11c4e8d88ac"
|
script: '76a914b9bbd76588d9e4e09f0369a9aa0b2749a11c4e8d88ac',
|
||||||
|
address: 'mxT2KzTUQvsaYYothDtjcdvyAdaHA9ofMp'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"satoshis": 53320000,
|
satoshis: 53320000,
|
||||||
"script": "76a914d2ec20bb8e5f25a52f730384b803d95683250e0b88ac"
|
address: 'mzkD4nmQ8ixqxySdBgsXTpgvAMK5iRZpNK',
|
||||||
|
script: '76a914d2ec20bb8e5f25a52f730384b803d95683250e0b88ac'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"satoshis": 289829,
|
address: 'moZY18rGNmh4YCPeugtGW46AkkWMQttBUD',
|
||||||
"script": "76a914583df9fa56ad961051e00ca93e68dfaf1eab9ec588ac"
|
satoshis: 289829,
|
||||||
|
script: '76a914583df9fa56ad961051e00ca93e68dfaf1eab9ec588ac'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"nLockTime": 0
|
locktime: 0
|
||||||
});
|
};
|
||||||
|
|
||||||
tx.__height = 534181;
|
|
||||||
tx.__timestamp = 1441116143;
|
|
||||||
tx.__blockHash = '0000000000000041ddc94ecf4f86a456a83b2e320c36c6f0c13ff92c7e75f013';
|
|
||||||
var txinfos2 = {
|
var txinfos2 = {
|
||||||
totalCount: 1,
|
totalCount: 1,
|
||||||
items: [
|
items: [
|
||||||
@ -149,24 +152,24 @@ var txinfos2 = {
|
|||||||
|
|
||||||
var utxos = [
|
var utxos = [
|
||||||
{
|
{
|
||||||
"address": "mzkD4nmQ8ixqxySdBgsXTpgvAMK5iRZpNK",
|
'address': 'mzkD4nmQ8ixqxySdBgsXTpgvAMK5iRZpNK',
|
||||||
"txid": "63b68becb0e514b32317f4b29a5cf0627d4087e54ac17f686fcb1d9a27680f73",
|
'txid': '63b68becb0e514b32317f4b29a5cf0627d4087e54ac17f686fcb1d9a27680f73',
|
||||||
"outputIndex": 1,
|
'outputIndex': 1,
|
||||||
"timestamp": 1441116143,
|
'timestamp': 1441116143,
|
||||||
"satoshis": 53320000,
|
'satoshis': 53320000,
|
||||||
"script": "76a914d2ec20bb8e5f25a52f730384b803d95683250e0b88ac",
|
'script': '76a914d2ec20bb8e5f25a52f730384b803d95683250e0b88ac',
|
||||||
"blockHeight": 534181,
|
'height': 534181,
|
||||||
"confirmations": 50
|
'confirmations': 50
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"address": "moZY18rGNmh4YCPeugtGW46AkkWMQttBUD",
|
'address': 'moZY18rGNmh4YCPeugtGW46AkkWMQttBUD',
|
||||||
"txid": "63b68becb0e514b32317f4b29a5cf0627d4087e54ac17f686fcb1d9a27680f73",
|
'txid': '63b68becb0e514b32317f4b29a5cf0627d4087e54ac17f686fcb1d9a27680f73',
|
||||||
"outputIndex": 2,
|
'outputIndex': 2,
|
||||||
"timestamp": 1441116143,
|
'timestamp': 1441116143,
|
||||||
"satoshis": 289829,
|
'satoshis': 289829,
|
||||||
"script": "76a914583df9fa56ad961051e00ca93e68dfaf1eab9ec588ac",
|
'script': '76a914583df9fa56ad961051e00ca93e68dfaf1eab9ec588ac',
|
||||||
"blockHeight": 534181,
|
'height': 534181,
|
||||||
"confirmations": 50
|
'confirmations': 50
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -196,21 +199,21 @@ describe('Addresses', function() {
|
|||||||
|
|
||||||
it('should have correct data', function(done) {
|
it('should have correct data', function(done) {
|
||||||
var insight = {
|
var insight = {
|
||||||
"addrStr": "mkPvAKZ2rar6qeG3KjBtJHHMSP1wFZH7Er",
|
'addrStr': 'mkPvAKZ2rar6qeG3KjBtJHHMSP1wFZH7Er',
|
||||||
"balance": 0,
|
'balance': 0,
|
||||||
"balanceSat": 0,
|
'balanceSat': 0,
|
||||||
"totalReceived": 27.82729129,
|
'totalReceived': 27.82729129,
|
||||||
"totalReceivedSat": 2782729129,
|
'totalReceivedSat': 2782729129,
|
||||||
"totalSent": 27.82729129,
|
'totalSent': 27.82729129,
|
||||||
"totalSentSat": 2782729129,
|
'totalSentSat': 2782729129,
|
||||||
"unconfirmedBalance": 0,
|
'unconfirmedBalance': 0,
|
||||||
"unconfirmedBalanceSat": 0,
|
'unconfirmedBalanceSat': 0,
|
||||||
"unconfirmedTxApperances": 0,
|
'unconfirmedTxApperances': 0,
|
||||||
"txApperances": 2,
|
'txApperances': 2,
|
||||||
"transactions": [
|
'transactions': [
|
||||||
"bb0ec3b96209fac9529570ea6f83a86af2cceedde4aaf2bfcc4796680d23f1c7",
|
'bb0ec3b96209fac9529570ea6f83a86af2cceedde4aaf2bfcc4796680d23f1c7',
|
||||||
"01f700df84c466f2a389440e5eeacdc47d04f380c39e5d19dce2ce91a11ecba3"
|
'01f700df84c466f2a389440e5eeacdc47d04f380c39e5d19dce2ce91a11ecba3'
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
var res = {
|
var res = {
|
||||||
@ -222,6 +225,30 @@ describe('Addresses', function() {
|
|||||||
addresses.show(req, res);
|
addresses.show(req, res);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('handle error', function() {
|
||||||
|
var testnode = {};
|
||||||
|
testnode.log = {};
|
||||||
|
testnode.log.error = sinon.stub();
|
||||||
|
var controller = new AddressController(testnode);
|
||||||
|
controller.getAddressSummary = sinon.stub().callsArgWith(2, new Error('test'));
|
||||||
|
var req = {
|
||||||
|
query: {
|
||||||
|
noTxList: 1
|
||||||
|
},
|
||||||
|
addr: 'mkPvAKZ2rar6qeG3KjBtJHHMSP1wFZH7Er'
|
||||||
|
};
|
||||||
|
var send = sinon.stub();
|
||||||
|
var status = sinon.stub().returns({send: send});
|
||||||
|
var res = {
|
||||||
|
status: status
|
||||||
|
};
|
||||||
|
controller.show(req, res);
|
||||||
|
send.callCount.should.equal(1);
|
||||||
|
status.callCount.should.equal(1);
|
||||||
|
status.args[0][0].should.equal(503);
|
||||||
|
send.args[0][0].should.equal('test');
|
||||||
|
});
|
||||||
|
|
||||||
it('/balance', function(done) {
|
it('/balance', function(done) {
|
||||||
var insight = 0;
|
var insight = 0;
|
||||||
|
|
||||||
@ -277,16 +304,18 @@ describe('Addresses', function() {
|
|||||||
describe('/addr/:addr/utxo', function() {
|
describe('/addr/:addr/utxo', function() {
|
||||||
it('should have correct data', function(done) {
|
it('should have correct data', function(done) {
|
||||||
var insight = [
|
var insight = [
|
||||||
{
|
{
|
||||||
"address": "mzkD4nmQ8ixqxySdBgsXTpgvAMK5iRZpNK",
|
'address': 'mzkD4nmQ8ixqxySdBgsXTpgvAMK5iRZpNK',
|
||||||
"txid": "63b68becb0e514b32317f4b29a5cf0627d4087e54ac17f686fcb1d9a27680f73",
|
'txid': '63b68becb0e514b32317f4b29a5cf0627d4087e54ac17f686fcb1d9a27680f73',
|
||||||
"vout": 1,
|
'vout': 1,
|
||||||
"ts": 1441116143,
|
'ts': 1441116143,
|
||||||
"scriptPubKey": "76a914d2ec20bb8e5f25a52f730384b803d95683250e0b88ac",
|
'scriptPubKey': '76a914d2ec20bb8e5f25a52f730384b803d95683250e0b88ac',
|
||||||
"amount": 0.5332,
|
'amount': 0.5332,
|
||||||
"confirmations": 50,
|
'confirmations': 50,
|
||||||
"confirmationsFromCache": true
|
'height': 534181,
|
||||||
}
|
'satoshis': 53320000,
|
||||||
|
'confirmationsFromCache': true
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
var todos = [
|
var todos = [
|
||||||
@ -296,7 +325,12 @@ describe('Addresses', function() {
|
|||||||
];
|
];
|
||||||
|
|
||||||
var node = {
|
var node = {
|
||||||
getUnspentOutputs: sinon.stub().callsArgWith(2, null, utxos.slice(0, 1))
|
services: {
|
||||||
|
bitcoind: {
|
||||||
|
height: 534230
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getAddressUnspentOutputs: sinon.stub().callsArgWith(2, null, utxos.slice(0, 1))
|
||||||
};
|
};
|
||||||
|
|
||||||
var addresses = new AddressController(node);
|
var addresses = new AddressController(node);
|
||||||
@ -320,26 +354,30 @@ describe('Addresses', function() {
|
|||||||
describe('/addrs/:addrs/utxo', function() {
|
describe('/addrs/:addrs/utxo', function() {
|
||||||
it('should have the correct data', function(done) {
|
it('should have the correct data', function(done) {
|
||||||
var insight = [
|
var insight = [
|
||||||
{
|
{
|
||||||
"address": "mzkD4nmQ8ixqxySdBgsXTpgvAMK5iRZpNK",
|
'address': 'mzkD4nmQ8ixqxySdBgsXTpgvAMK5iRZpNK',
|
||||||
"txid": "63b68becb0e514b32317f4b29a5cf0627d4087e54ac17f686fcb1d9a27680f73",
|
'txid': '63b68becb0e514b32317f4b29a5cf0627d4087e54ac17f686fcb1d9a27680f73',
|
||||||
"vout": 1,
|
'vout': 1,
|
||||||
"ts": 1441116143,
|
'ts': 1441116143,
|
||||||
"scriptPubKey": "76a914d2ec20bb8e5f25a52f730384b803d95683250e0b88ac",
|
'scriptPubKey': '76a914d2ec20bb8e5f25a52f730384b803d95683250e0b88ac',
|
||||||
"amount": 0.5332,
|
'amount': 0.5332,
|
||||||
"confirmations": 50,
|
'height': 534181,
|
||||||
"confirmationsFromCache": true
|
'satoshis': 53320000,
|
||||||
},
|
'confirmations': 50,
|
||||||
{
|
'confirmationsFromCache': true
|
||||||
"address": "moZY18rGNmh4YCPeugtGW46AkkWMQttBUD",
|
},
|
||||||
"txid": "63b68becb0e514b32317f4b29a5cf0627d4087e54ac17f686fcb1d9a27680f73",
|
{
|
||||||
"vout": 2,
|
'address': 'moZY18rGNmh4YCPeugtGW46AkkWMQttBUD',
|
||||||
"ts": 1441116143,
|
'txid': '63b68becb0e514b32317f4b29a5cf0627d4087e54ac17f686fcb1d9a27680f73',
|
||||||
"scriptPubKey": "76a914583df9fa56ad961051e00ca93e68dfaf1eab9ec588ac",
|
'vout': 2,
|
||||||
"amount": 0.00289829,
|
'ts': 1441116143,
|
||||||
"confirmations": 50,
|
'scriptPubKey': '76a914583df9fa56ad961051e00ca93e68dfaf1eab9ec588ac',
|
||||||
"confirmationsFromCache": true
|
'amount': 0.00289829,
|
||||||
}
|
'height': 534181,
|
||||||
|
'satoshis': 289829,
|
||||||
|
'confirmations': 50,
|
||||||
|
'confirmationsFromCache': true
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
var todos = [
|
var todos = [
|
||||||
@ -351,7 +389,12 @@ describe('Addresses', function() {
|
|||||||
];
|
];
|
||||||
|
|
||||||
var node = {
|
var node = {
|
||||||
getUnspentOutputs: sinon.stub().callsArgWith(2, null, utxos)
|
services: {
|
||||||
|
bitcoind: {
|
||||||
|
height: 534230
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getAddressUnspentOutputs: sinon.stub().callsArgWith(2, null, utxos)
|
||||||
};
|
};
|
||||||
|
|
||||||
var addresses = new AddressController(node);
|
var addresses = new AddressController(node);
|
||||||
@ -375,141 +418,150 @@ describe('Addresses', function() {
|
|||||||
describe('/addrs/:addrs/txs', function() {
|
describe('/addrs/:addrs/txs', function() {
|
||||||
it('should have correct data', function(done) {
|
it('should have correct data', function(done) {
|
||||||
var insight = {
|
var insight = {
|
||||||
"totalItems": 1,
|
'totalItems': 1,
|
||||||
"from": 0,
|
'from': 0,
|
||||||
"to": 1,
|
'to': 1,
|
||||||
"items": [
|
'items': [
|
||||||
|
{
|
||||||
|
'txid': '63b68becb0e514b32317f4b29a5cf0627d4087e54ac17f686fcb1d9a27680f73',
|
||||||
|
'version': 1,
|
||||||
|
'locktime': 0,
|
||||||
|
'vin': [
|
||||||
{
|
{
|
||||||
"txid": "63b68becb0e514b32317f4b29a5cf0627d4087e54ac17f686fcb1d9a27680f73",
|
'txid': 'ea97726ffc529808094ae5568342267931a058375a20147535a0d095837079f3',
|
||||||
"version": 1,
|
'vout': 1,
|
||||||
"locktime": 0,
|
'scriptSig': {
|
||||||
"vin": [
|
'asm': '3044022054233934268b30be779fad874ef42e8db928ba27a1b612d5f111b3ee95eb271c022024272bbaf2dcc4050bd3b9dfa3c93884f6ba6ad7d257598b8245abb65b5ab1e401 040682fdb281a8533e21e13dfd1fcfa424912a85b6cdc4136b5842c85de05ac1f0e4a013f20702adeb53329de13b2ef388e5ed6244676f4f1ee4ee685ab607964d',
|
||||||
{
|
'hex': '473044022054233934268b30be779fad874ef42e8db928ba27a1b612d5f111b3ee95eb271c022024272bbaf2dcc4050bd3b9dfa3c93884f6ba6ad7d257598b8245abb65b5ab1e40141040682fdb281a8533e21e13dfd1fcfa424912a85b6cdc4136b5842c85de05ac1f0e4a013f20702adeb53329de13b2ef388e5ed6244676f4f1ee4ee685ab607964d'
|
||||||
"txid": "ea97726ffc529808094ae5568342267931a058375a20147535a0d095837079f3",
|
},
|
||||||
"vout": 1,
|
'sequence': 4294967295,
|
||||||
"scriptSig": {
|
'n': 0,
|
||||||
"asm": "3044022054233934268b30be779fad874ef42e8db928ba27a1b612d5f111b3ee95eb271c022024272bbaf2dcc4050bd3b9dfa3c93884f6ba6ad7d257598b8245abb65b5ab1e401 040682fdb281a8533e21e13dfd1fcfa424912a85b6cdc4136b5842c85de05ac1f0e4a013f20702adeb53329de13b2ef388e5ed6244676f4f1ee4ee685ab607964d",
|
'addr': 'moFfnRwt77pApKnnU6m5uocFaa43aAYpt5',
|
||||||
"hex": "473044022054233934268b30be779fad874ef42e8db928ba27a1b612d5f111b3ee95eb271c022024272bbaf2dcc4050bd3b9dfa3c93884f6ba6ad7d257598b8245abb65b5ab1e40141040682fdb281a8533e21e13dfd1fcfa424912a85b6cdc4136b5842c85de05ac1f0e4a013f20702adeb53329de13b2ef388e5ed6244676f4f1ee4ee685ab607964d"
|
'valueSat': 53540000,
|
||||||
},
|
'value': 0.5354,
|
||||||
"sequence": 4294967295,
|
'doubleSpentTxID': null
|
||||||
"n": 0,
|
},
|
||||||
"addr": "moFfnRwt77pApKnnU6m5uocFaa43aAYpt5",
|
{
|
||||||
"valueSat": 53540000,
|
'txid': '980a9cc2dbc2d3464eb9900ae6d579a03045408563320f62d99316c3d4ff58b7',
|
||||||
"value": 0.5354,
|
'vout': 2,
|
||||||
"doubleSpentTxID": null
|
'scriptSig': {
|
||||||
},
|
'asm': '3044022044938ac3f8fcb8da29011df6397ed28cc7e894cdc35d596d4f3623bd8c7e465f022014829c6e0bd7ee97a1bcfef6b85c5fd232653f289394fc6ce6ebb41c73403f1b01 04d9ccf88efc6e5be3151fae5e848efd94c91d75e7bf621f9f724a8caff51415338525d3239fae6b93826edf759dd562f77693e55dfa852ffd96a92d683db590f2',
|
||||||
{
|
'hex': '473044022044938ac3f8fcb8da29011df6397ed28cc7e894cdc35d596d4f3623bd8c7e465f022014829c6e0bd7ee97a1bcfef6b85c5fd232653f289394fc6ce6ebb41c73403f1b014104d9ccf88efc6e5be3151fae5e848efd94c91d75e7bf621f9f724a8caff51415338525d3239fae6b93826edf759dd562f77693e55dfa852ffd96a92d683db590f2'
|
||||||
"txid": "980a9cc2dbc2d3464eb9900ae6d579a03045408563320f62d99316c3d4ff58b7",
|
},
|
||||||
"vout": 2,
|
'sequence': 4294967295,
|
||||||
"scriptSig": {
|
'n': 1,
|
||||||
"asm": "3044022044938ac3f8fcb8da29011df6397ed28cc7e894cdc35d596d4f3623bd8c7e465f022014829c6e0bd7ee97a1bcfef6b85c5fd232653f289394fc6ce6ebb41c73403f1b01 04d9ccf88efc6e5be3151fae5e848efd94c91d75e7bf621f9f724a8caff51415338525d3239fae6b93826edf759dd562f77693e55dfa852ffd96a92d683db590f2",
|
'addr': 'n1XJBAyU4hNR4xRtY3UxnmAteoJX83p5qv',
|
||||||
"hex": "473044022044938ac3f8fcb8da29011df6397ed28cc7e894cdc35d596d4f3623bd8c7e465f022014829c6e0bd7ee97a1bcfef6b85c5fd232653f289394fc6ce6ebb41c73403f1b014104d9ccf88efc6e5be3151fae5e848efd94c91d75e7bf621f9f724a8caff51415338525d3239fae6b93826edf759dd562f77693e55dfa852ffd96a92d683db590f2"
|
'valueSat': 299829,
|
||||||
},
|
'value': 0.00299829,
|
||||||
"sequence": 4294967295,
|
'doubleSpentTxID': null
|
||||||
"n": 1,
|
|
||||||
"addr": "n1XJBAyU4hNR4xRtY3UxnmAteoJX83p5qv",
|
|
||||||
"valueSat": 299829,
|
|
||||||
"value": 0.00299829,
|
|
||||||
"doubleSpentTxID": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"vout": [
|
|
||||||
{
|
|
||||||
"value": "0.00220000",
|
|
||||||
"n": 0,
|
|
||||||
"scriptPubKey": {
|
|
||||||
"asm": "OP_DUP OP_HASH160 b9bbd76588d9e4e09f0369a9aa0b2749a11c4e8d OP_EQUALVERIFY OP_CHECKSIG",
|
|
||||||
"hex": "76a914b9bbd76588d9e4e09f0369a9aa0b2749a11c4e8d88ac",
|
|
||||||
"reqSigs": 1,
|
|
||||||
"type": "pubkeyhash",
|
|
||||||
"addresses": [
|
|
||||||
"mxT2KzTUQvsaYYothDtjcdvyAdaHA9ofMp"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "0.53320000",
|
|
||||||
"n": 1,
|
|
||||||
"scriptPubKey": {
|
|
||||||
"asm": "OP_DUP OP_HASH160 d2ec20bb8e5f25a52f730384b803d95683250e0b OP_EQUALVERIFY OP_CHECKSIG",
|
|
||||||
"hex": "76a914d2ec20bb8e5f25a52f730384b803d95683250e0b88ac",
|
|
||||||
"reqSigs": 1,
|
|
||||||
"type": "pubkeyhash",
|
|
||||||
"addresses": [
|
|
||||||
"mzkD4nmQ8ixqxySdBgsXTpgvAMK5iRZpNK"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "0.00289829",
|
|
||||||
"n": 2,
|
|
||||||
"scriptPubKey": {
|
|
||||||
"asm": "OP_DUP OP_HASH160 583df9fa56ad961051e00ca93e68dfaf1eab9ec5 OP_EQUALVERIFY OP_CHECKSIG",
|
|
||||||
"hex": "76a914583df9fa56ad961051e00ca93e68dfaf1eab9ec588ac",
|
|
||||||
"reqSigs": 1,
|
|
||||||
"type": "pubkeyhash",
|
|
||||||
"addresses": [
|
|
||||||
"moZY18rGNmh4YCPeugtGW46AkkWMQttBUD"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"blockhash": "0000000000000041ddc94ecf4f86a456a83b2e320c36c6f0c13ff92c7e75f013",
|
|
||||||
"blockheight": 534181,
|
|
||||||
"confirmations": 52,
|
|
||||||
"time": 1441116143,
|
|
||||||
"blocktime": 1441116143,
|
|
||||||
"valueOut": 0.53829829,
|
|
||||||
"size": 470,
|
|
||||||
"valueIn": 0.53839829,
|
|
||||||
"fees": 0.0001,
|
|
||||||
"firstSeenTs": 1441108193
|
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
'vout': [
|
||||||
|
{
|
||||||
|
'value': '0.00220000',
|
||||||
|
'n': 0,
|
||||||
|
'scriptPubKey': {
|
||||||
|
'asm': 'OP_DUP OP_HASH160 b9bbd76588d9e4e09f0369a9aa0b2749a11c4e8d OP_EQUALVERIFY OP_CHECKSIG',
|
||||||
|
'hex': '76a914b9bbd76588d9e4e09f0369a9aa0b2749a11c4e8d88ac',
|
||||||
|
'reqSigs': 1,
|
||||||
|
'type': 'pubkeyhash',
|
||||||
|
'addresses': [
|
||||||
|
'mxT2KzTUQvsaYYothDtjcdvyAdaHA9ofMp'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'spentHeight': null,
|
||||||
|
'spentIndex': null,
|
||||||
|
'spentTxId': null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'value': '0.53320000',
|
||||||
|
'n': 1,
|
||||||
|
'scriptPubKey': {
|
||||||
|
'asm': 'OP_DUP OP_HASH160 d2ec20bb8e5f25a52f730384b803d95683250e0b OP_EQUALVERIFY OP_CHECKSIG',
|
||||||
|
'hex': '76a914d2ec20bb8e5f25a52f730384b803d95683250e0b88ac',
|
||||||
|
'reqSigs': 1,
|
||||||
|
'type': 'pubkeyhash',
|
||||||
|
'addresses': [
|
||||||
|
'mzkD4nmQ8ixqxySdBgsXTpgvAMK5iRZpNK'
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'spentHeight': null,
|
||||||
|
'spentIndex': null,
|
||||||
|
'spentTxId': null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'value': '0.00289829',
|
||||||
|
'n': 2,
|
||||||
|
'scriptPubKey': {
|
||||||
|
'asm': 'OP_DUP OP_HASH160 583df9fa56ad961051e00ca93e68dfaf1eab9ec5 OP_EQUALVERIFY OP_CHECKSIG',
|
||||||
|
'hex': '76a914583df9fa56ad961051e00ca93e68dfaf1eab9ec588ac',
|
||||||
|
'reqSigs': 1,
|
||||||
|
'type': 'pubkeyhash',
|
||||||
|
'addresses': [
|
||||||
|
'moZY18rGNmh4YCPeugtGW46AkkWMQttBUD'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'spentHeight': null,
|
||||||
|
'spentIndex': null,
|
||||||
|
'spentTxId': null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'blockhash': '0000000000000041ddc94ecf4f86a456a83b2e320c36c6f0c13ff92c7e75f013',
|
||||||
|
'blockheight': 534181,
|
||||||
|
'confirmations': 52,
|
||||||
|
'time': 1441116143,
|
||||||
|
'blocktime': 1441116143,
|
||||||
|
'valueOut': 0.53829829,
|
||||||
|
'size': 470,
|
||||||
|
'valueIn': 0.53839829,
|
||||||
|
'fees': 0.0001,
|
||||||
|
'firstSeenTs': 1441108193
|
||||||
|
}
|
||||||
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
var todos = {
|
var todos = {
|
||||||
"items": [
|
'items': [
|
||||||
{
|
{
|
||||||
"vin": [
|
'vin': [
|
||||||
{
|
{
|
||||||
"scriptSig": {
|
'scriptSig': {
|
||||||
"asm": "3044022054233934268b30be779fad874ef42e8db928ba27a1b612d5f111b3ee95eb271c022024272bbaf2dcc4050bd3b9dfa3c93884f6ba6ad7d257598b8245abb65b5ab1e401 040682fdb281a8533e21e13dfd1fcfa424912a85b6cdc4136b5842c85de05ac1f0e4a013f20702adeb53329de13b2ef388e5ed6244676f4f1ee4ee685ab607964d"
|
'asm': '3044022054233934268b30be779fad874ef42e8db928ba27a1b612d5f111b3ee95eb271c022024272bbaf2dcc4050bd3b9dfa3c93884f6ba6ad7d257598b8245abb65b5ab1e401 040682fdb281a8533e21e13dfd1fcfa424912a85b6cdc4136b5842c85de05ac1f0e4a013f20702adeb53329de13b2ef388e5ed6244676f4f1ee4ee685ab607964d'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"scriptSig": {
|
'scriptSig': {
|
||||||
"asm": "3044022044938ac3f8fcb8da29011df6397ed28cc7e894cdc35d596d4f3623bd8c7e465f022014829c6e0bd7ee97a1bcfef6b85c5fd232653f289394fc6ce6ebb41c73403f1b01 04d9ccf88efc6e5be3151fae5e848efd94c91d75e7bf621f9f724a8caff51415338525d3239fae6b93826edf759dd562f77693e55dfa852ffd96a92d683db590f2"
|
'asm': '3044022044938ac3f8fcb8da29011df6397ed28cc7e894cdc35d596d4f3623bd8c7e465f022014829c6e0bd7ee97a1bcfef6b85c5fd232653f289394fc6ce6ebb41c73403f1b01 04d9ccf88efc6e5be3151fae5e848efd94c91d75e7bf621f9f724a8caff51415338525d3239fae6b93826edf759dd562f77693e55dfa852ffd96a92d683db590f2'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"vout": [
|
'vout': [
|
||||||
{
|
{
|
||||||
"scriptPubKey": {
|
'scriptPubKey': {
|
||||||
"asm": "OP_DUP OP_HASH160 b9bbd76588d9e4e09f0369a9aa0b2749a11c4e8d OP_EQUALVERIFY OP_CHECKSIG",
|
'asm': 'OP_DUP OP_HASH160 b9bbd76588d9e4e09f0369a9aa0b2749a11c4e8d OP_EQUALVERIFY OP_CHECKSIG',
|
||||||
"reqSigs": 1,
|
'reqSigs': 1,
|
||||||
"type": "pubkeyhash",
|
'type': 'pubkeyhash',
|
||||||
"addresses": []
|
'addresses': []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"scriptPubKey": {
|
'scriptPubKey': {
|
||||||
"asm": "OP_DUP OP_HASH160 d2ec20bb8e5f25a52f730384b803d95683250e0b OP_EQUALVERIFY OP_CHECKSIG",
|
'asm': 'OP_DUP OP_HASH160 d2ec20bb8e5f25a52f730384b803d95683250e0b OP_EQUALVERIFY OP_CHECKSIG',
|
||||||
"reqSigs": 1,
|
'reqSigs': 1,
|
||||||
"type": "pubkeyhash",
|
'type': 'pubkeyhash',
|
||||||
"addresses": []
|
'addresses': []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"scriptPubKey": {
|
'scriptPubKey': {
|
||||||
"asm": "OP_DUP OP_HASH160 583df9fa56ad961051e00ca93e68dfaf1eab9ec5 OP_EQUALVERIFY OP_CHECKSIG",
|
'asm': 'OP_DUP OP_HASH160 583df9fa56ad961051e00ca93e68dfaf1eab9ec5 OP_EQUALVERIFY OP_CHECKSIG',
|
||||||
"reqSigs": 1,
|
'reqSigs': 1,
|
||||||
"type": "pubkeyhash",
|
'type': 'pubkeyhash',
|
||||||
"addresses": []
|
'addresses': []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"firstSeenTs": 1441108193
|
'firstSeenTs': 1441108193
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
@ -517,13 +569,8 @@ describe('Addresses', function() {
|
|||||||
var node = {
|
var node = {
|
||||||
getAddressHistory: sinon.stub().callsArgWith(2, null, txinfos2),
|
getAddressHistory: sinon.stub().callsArgWith(2, null, txinfos2),
|
||||||
services: {
|
services: {
|
||||||
db: {
|
bitcoind: {
|
||||||
tip: {
|
height: 534232
|
||||||
__height: 534232
|
|
||||||
}
|
|
||||||
},
|
|
||||||
address: {
|
|
||||||
getInputForOutput: sinon.stub().callsArgWith(3, null, false),
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
network: 'testnet'
|
network: 'testnet'
|
||||||
|
|||||||
154
test/blocks.js
154
test/blocks.js
@ -13,12 +13,15 @@ var blockIndexes = {
|
|||||||
hash: '0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7',
|
hash: '0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7',
|
||||||
chainWork: '0000000000000000000000000000000000000000000000054626b1839ade284a',
|
chainWork: '0000000000000000000000000000000000000000000000054626b1839ade284a',
|
||||||
prevHash: '00000000000001a55f3214e9172eb34b20e0bc5bd6b8007f3f149fca2c8991a4',
|
prevHash: '00000000000001a55f3214e9172eb34b20e0bc5bd6b8007f3f149fca2c8991a4',
|
||||||
|
nextHash: '000000000001e866a8057cde0c650796cb8a59e0e6038dc31c69d7ca6649627d',
|
||||||
|
confirmations: 119,
|
||||||
height: 533974
|
height: 533974
|
||||||
},
|
},
|
||||||
'000000000008fbb2e358e382a6f6948b2da24563bba183af447e6e2542e8efc7': {
|
'000000000008fbb2e358e382a6f6948b2da24563bba183af447e6e2542e8efc7': {
|
||||||
hash: '000000000008fbb2e358e382a6f6948b2da24563bba183af447e6e2542e8efc7',
|
hash: '000000000008fbb2e358e382a6f6948b2da24563bba183af447e6e2542e8efc7',
|
||||||
chainWork: '00000000000000000000000000000000000000000000000544ea52e1575ca753',
|
chainWork: '00000000000000000000000000000000000000000000000544ea52e1575ca753',
|
||||||
prevHash: '00000000000006bd8fe9e53780323c0e85719eca771022e1eb6d10c62195c441',
|
prevHash: '00000000000006bd8fe9e53780323c0e85719eca771022e1eb6d10c62195c441',
|
||||||
|
confirmations: 119,
|
||||||
height: 533951
|
height: 533951
|
||||||
},
|
},
|
||||||
'00000000000006bd8fe9e53780323c0e85719eca771022e1eb6d10c62195c441': {
|
'00000000000006bd8fe9e53780323c0e85719eca771022e1eb6d10c62195c441': {
|
||||||
@ -50,9 +53,9 @@ describe('Blocks', function() {
|
|||||||
'version': 536870919,
|
'version': 536870919,
|
||||||
'merkleroot': 'b06437355844b8178173f3e18ca141472e4b0861daa81ef0f701cf9e51f0283e',
|
'merkleroot': 'b06437355844b8178173f3e18ca141472e4b0861daa81ef0f701cf9e51f0283e',
|
||||||
'tx': [
|
'tx': [
|
||||||
'25a988e54b02e0e5df146a0f8fa7b9db56210533a9f04bdfda5f4ceb6f77aadd',
|
'25a988e54b02e0e5df146a0f8fa7b9db56210533a9f04bdfda5f4ceb6f77aadd',
|
||||||
'b85334bf2df35c6dd5b294efe92ffc793a78edff75a2ca666fc296ffb04bbba0',
|
'b85334bf2df35c6dd5b294efe92ffc793a78edff75a2ca666fc296ffb04bbba0',
|
||||||
'2e01c7a4a0e335112236b711c4aaddd02e8dc59ba2cda416e8f80ff06dddd7e1'
|
'2e01c7a4a0e335112236b711c4aaddd02e8dc59ba2cda416e8f80ff06dddd7e1'
|
||||||
],
|
],
|
||||||
'time': 1440987503,
|
'time': 1440987503,
|
||||||
'nonce': 1868753784,
|
'nonce': 1868753784,
|
||||||
@ -69,24 +72,25 @@ describe('Blocks', function() {
|
|||||||
var bitcoreBlock = bitcore.Block.fromBuffer(new Buffer(blocks['0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7'], 'hex'));
|
var bitcoreBlock = bitcore.Block.fromBuffer(new Buffer(blocks['0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7'], 'hex'));
|
||||||
|
|
||||||
var node = {
|
var node = {
|
||||||
|
log: sinon.stub(),
|
||||||
getBlock: sinon.stub().callsArgWith(1, null, bitcoreBlock),
|
getBlock: sinon.stub().callsArgWith(1, null, bitcoreBlock),
|
||||||
services: {
|
services: {
|
||||||
bitcoind: {
|
bitcoind: {
|
||||||
getNextBlockHash: sinon.stub().returns('000000000001e866a8057cde0c650796cb8a59e0e6038dc31c69d7ca6649627d'),
|
getBlockHeader: sinon.stub().callsArgWith(1, null, blockIndexes['0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7']),
|
||||||
getBlockIndex: sinon.stub().returns(blockIndexes['0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7']),
|
isMainChain: sinon.stub().returns(true),
|
||||||
isMainChain: sinon.stub().returns(true)
|
height: 534092
|
||||||
},
|
|
||||||
db: {
|
|
||||||
tip: {
|
|
||||||
__height: 534092
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
it('block data should be correct', function(done) {
|
it('block data should be correct', function(done) {
|
||||||
var controller = new BlockController(node);
|
var controller = new BlockController({node: node});
|
||||||
var req = {};
|
var hash = '0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7';
|
||||||
|
var req = {
|
||||||
|
params: {
|
||||||
|
blockHash: hash
|
||||||
|
}
|
||||||
|
};
|
||||||
var res = {};
|
var res = {};
|
||||||
var next = function() {
|
var next = function() {
|
||||||
should.exist(req.block);
|
should.exist(req.block);
|
||||||
@ -94,31 +98,28 @@ describe('Blocks', function() {
|
|||||||
should(block).eql(insight);
|
should(block).eql(insight);
|
||||||
done();
|
done();
|
||||||
};
|
};
|
||||||
|
controller.block(req, res, next);
|
||||||
var hash = '0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7';
|
|
||||||
|
|
||||||
controller.block(req, res, next, hash);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('block pool info should be correct', function(done) {
|
it('block pool info should be correct', function(done) {
|
||||||
var block = bitcore.Block.fromString(blocks['000000000000000004a118407a4e3556ae2d5e882017e7ce526659d8073f13a4']);
|
var block = bitcore.Block.fromString(blocks['000000000000000004a118407a4e3556ae2d5e882017e7ce526659d8073f13a4']);
|
||||||
var node = {
|
var node = {
|
||||||
|
log: sinon.stub(),
|
||||||
getBlock: sinon.stub().callsArgWith(1, null, block),
|
getBlock: sinon.stub().callsArgWith(1, null, block),
|
||||||
services: {
|
services: {
|
||||||
bitcoind: {
|
bitcoind: {
|
||||||
getNextBlockHash: sinon.stub().returns('000000000001e866a8057cde0c650796cb8a59e0e6038dc31c69d7ca6649627d'),
|
getBlockHeader: sinon.stub().callsArgWith(1, null, blockIndexes['000000000000000004a118407a4e3556ae2d5e882017e7ce526659d8073f13a4']),
|
||||||
getBlockIndex: sinon.stub().returns(blockIndexes['000000000000000004a118407a4e3556ae2d5e882017e7ce526659d8073f13a4']),
|
isMainChain: sinon.stub().returns(true),
|
||||||
isMainChain: sinon.stub().returns(true)
|
height: 534092
|
||||||
},
|
|
||||||
db: {
|
|
||||||
tip: {
|
|
||||||
__height: 534092
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var controller = new BlockController(node);
|
var controller = new BlockController({node: node});
|
||||||
var req = {};
|
var req = {
|
||||||
|
params: {
|
||||||
|
blockHash: hash
|
||||||
|
}
|
||||||
|
};
|
||||||
var res = {};
|
var res = {};
|
||||||
var next = function() {
|
var next = function() {
|
||||||
should.exist(req.block);
|
should.exist(req.block);
|
||||||
@ -130,7 +131,7 @@ describe('Blocks', function() {
|
|||||||
|
|
||||||
var hash = '000000000000000004a118407a4e3556ae2d5e882017e7ce526659d8073f13a4';
|
var hash = '000000000000000004a118407a4e3556ae2d5e882017e7ce526659d8073f13a4';
|
||||||
|
|
||||||
controller.block(req, res, next, hash);
|
controller.block(req, res, next);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
@ -138,65 +139,64 @@ describe('Blocks', function() {
|
|||||||
describe('/blocks route', function() {
|
describe('/blocks route', function() {
|
||||||
|
|
||||||
var insight = {
|
var insight = {
|
||||||
"blocks": [
|
'blocks': [
|
||||||
{
|
{
|
||||||
"height": 533951,
|
'height': 533951,
|
||||||
"size": 206,
|
'size': 206,
|
||||||
"hash": "000000000008fbb2e358e382a6f6948b2da24563bba183af447e6e2542e8efc7",
|
'hash': '000000000008fbb2e358e382a6f6948b2da24563bba183af447e6e2542e8efc7',
|
||||||
"time": 1440978683,
|
'time': 1440978683,
|
||||||
"txlength": 1,
|
'txlength': 1,
|
||||||
"poolInfo": {
|
'poolInfo': {
|
||||||
"poolName": "AntMiner",
|
'poolName': 'AntMiner',
|
||||||
"url": "https://bitmaintech.com/"
|
'url': 'https://bitmaintech.com/'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"height": 533950,
|
'height': 533950,
|
||||||
"size": 206,
|
'size': 206,
|
||||||
"hash": "00000000000006bd8fe9e53780323c0e85719eca771022e1eb6d10c62195c441",
|
'hash': '00000000000006bd8fe9e53780323c0e85719eca771022e1eb6d10c62195c441',
|
||||||
"time": 1440977479,
|
'time': 1440977479,
|
||||||
"txlength": 1,
|
'txlength': 1,
|
||||||
"poolInfo": {
|
'poolInfo': {
|
||||||
"poolName": "AntMiner",
|
'poolName': 'AntMiner',
|
||||||
"url": "https://bitmaintech.com/"
|
'url': 'https://bitmaintech.com/'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"length": 2,
|
'length': 2,
|
||||||
"pagination": {
|
'pagination': {
|
||||||
"current": "2015-08-30",
|
'current': '2015-08-30',
|
||||||
"currentTs": 1440979199,
|
'currentTs': 1440979199,
|
||||||
"isToday": false,
|
'isToday': false,
|
||||||
"more": false,
|
'more': false,
|
||||||
"next": "2015-08-31",
|
'next': '2015-08-31',
|
||||||
"prev": "2015-08-29"
|
'prev': '2015-08-29'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var stub = sinon.stub();
|
var stub = sinon.stub();
|
||||||
stub.onFirstCall().callsArgWith(1, null, bitcore.Block.fromBuffer(blocks['000000000008fbb2e358e382a6f6948b2da24563bba183af447e6e2542e8efc7'], 'hex'));
|
stub.onFirstCall().callsArgWith(1, null, new Buffer(blocks['000000000008fbb2e358e382a6f6948b2da24563bba183af447e6e2542e8efc7'], 'hex'));
|
||||||
stub.onSecondCall().callsArgWith(1, null, bitcore.Block.fromBuffer(blocks['00000000000006bd8fe9e53780323c0e85719eca771022e1eb6d10c62195c441'], 'hex'))
|
stub.onSecondCall().callsArgWith(1, null, new Buffer(blocks['00000000000006bd8fe9e53780323c0e85719eca771022e1eb6d10c62195c441'], 'hex'));
|
||||||
|
|
||||||
var hashes = [
|
var hashes = [
|
||||||
'000000000008fbb2e358e382a6f6948b2da24563bba183af447e6e2542e8efc7',
|
'00000000000006bd8fe9e53780323c0e85719eca771022e1eb6d10c62195c441',
|
||||||
'00000000000006bd8fe9e53780323c0e85719eca771022e1eb6d10c62195c441'
|
'000000000008fbb2e358e382a6f6948b2da24563bba183af447e6e2542e8efc7'
|
||||||
];
|
];
|
||||||
var node = {
|
var node = {
|
||||||
getBlock: stub,
|
log: sinon.stub(),
|
||||||
services: {
|
services: {
|
||||||
bitcoind: {
|
bitcoind: {
|
||||||
getBlockIndex: function(hash) {
|
getRawBlock: stub,
|
||||||
return blockIndexes[hash];
|
getBlockHeader: function(hash, callback) {
|
||||||
}
|
callback(null, blockIndexes[hash]);
|
||||||
},
|
},
|
||||||
db: {
|
|
||||||
getBlockHashesByTimestamp: sinon.stub().callsArgWith(2, null, hashes)
|
getBlockHashesByTimestamp: sinon.stub().callsArgWith(2, null, hashes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
it('should have correct data', function(done) {
|
it('should have correct data', function(done) {
|
||||||
var blocks = new BlockController(node);
|
var blocks = new BlockController({node: node});
|
||||||
|
|
||||||
var req = {
|
var req = {
|
||||||
query: {
|
query: {
|
||||||
@ -218,38 +218,46 @@ describe('Blocks', function() {
|
|||||||
|
|
||||||
describe('/block-index/:height route', function() {
|
describe('/block-index/:height route', function() {
|
||||||
var node = {
|
var node = {
|
||||||
|
log: sinon.stub(),
|
||||||
services: {
|
services: {
|
||||||
bitcoind: {
|
bitcoind: {
|
||||||
getBlockIndex: function(height) {
|
getBlockHeader: function(height, callback) {
|
||||||
return blockIndexes[height];
|
callback(null, blockIndexes[height]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
it('should have correct data', function(done) {
|
it('should have correct data', function(done) {
|
||||||
var blocks = new BlockController(node);
|
var blocks = new BlockController({node: node});
|
||||||
|
|
||||||
var insight = {
|
var insight = {
|
||||||
"blockHash": "0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7"
|
'blockHash': '0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7'
|
||||||
};
|
};
|
||||||
|
|
||||||
var req = {};
|
var height = 533974;
|
||||||
|
|
||||||
|
var req = {
|
||||||
|
params: {
|
||||||
|
height: height
|
||||||
|
}
|
||||||
|
};
|
||||||
var res = {
|
var res = {
|
||||||
jsonp: function(data) {
|
jsonp: function(data) {
|
||||||
should(data).eql(insight);
|
should(data).eql(insight);
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var next = function() {};
|
|
||||||
var height = 533974;
|
|
||||||
|
|
||||||
blocks.blockIndex(req, res, next, height);
|
blocks.blockIndex(req, res);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#getBlockReward', function() {
|
describe('#getBlockReward', function() {
|
||||||
var blocks = new BlockController({});
|
var node = {
|
||||||
|
log: sinon.stub()
|
||||||
|
};
|
||||||
|
var blocks = new BlockController({node: node});
|
||||||
|
|
||||||
it('should give a block reward of 50 * 1e8 for block before first halvening', function() {
|
it('should give a block reward of 50 * 1e8 for block before first halvening', function() {
|
||||||
blocks.getBlockReward(100000).should.equal(50 * 1e8);
|
blocks.getBlockReward(100000).should.equal(50 * 1e8);
|
||||||
|
|||||||
@ -7,8 +7,12 @@ var InsightAPI = require('../lib/index');
|
|||||||
describe('Index', function() {
|
describe('Index', function() {
|
||||||
describe('#cache', function() {
|
describe('#cache', function() {
|
||||||
it('will set cache control header', function(done) {
|
it('will set cache control header', function(done) {
|
||||||
|
var node = {
|
||||||
|
log: sinon.stub()
|
||||||
|
};
|
||||||
var index = new InsightAPI({
|
var index = new InsightAPI({
|
||||||
enableCache: true
|
enableCache: true,
|
||||||
|
node: node
|
||||||
});
|
});
|
||||||
var req = {};
|
var req = {};
|
||||||
var res = {
|
var res = {
|
||||||
@ -23,8 +27,12 @@ describe('Index', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('will NOT set cache control header', function(done) {
|
it('will NOT set cache control header', function(done) {
|
||||||
|
var node = {
|
||||||
|
log: sinon.stub()
|
||||||
|
};
|
||||||
var index = new InsightAPI({
|
var index = new InsightAPI({
|
||||||
enableCache: false
|
enableCache: false,
|
||||||
|
node: node
|
||||||
});
|
});
|
||||||
var req = {};
|
var req = {};
|
||||||
var res = {
|
var res = {
|
||||||
@ -39,9 +47,13 @@ describe('Index', function() {
|
|||||||
});
|
});
|
||||||
describe('#cacheShort', function() {
|
describe('#cacheShort', function() {
|
||||||
it('will set SHORT cache control header', function(done) {
|
it('will set SHORT cache control header', function(done) {
|
||||||
|
var node = {
|
||||||
|
log: sinon.stub()
|
||||||
|
};
|
||||||
var index = new InsightAPI({
|
var index = new InsightAPI({
|
||||||
enableCache: true,
|
enableCache: true,
|
||||||
cacheShortSeconds: 35
|
cacheShortSeconds: 35,
|
||||||
|
node: node
|
||||||
});
|
});
|
||||||
var req = {};
|
var req = {};
|
||||||
var res = {
|
var res = {
|
||||||
@ -56,8 +68,12 @@ describe('Index', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('will set SHORT DEFAULT cache control header', function(done) {
|
it('will set SHORT DEFAULT cache control header', function(done) {
|
||||||
|
var node = {
|
||||||
|
log: sinon.stub()
|
||||||
|
};
|
||||||
var index = new InsightAPI({
|
var index = new InsightAPI({
|
||||||
enableCache: true
|
enableCache: true,
|
||||||
|
node: node
|
||||||
});
|
});
|
||||||
var req = {};
|
var req = {};
|
||||||
var res = {
|
var res = {
|
||||||
@ -74,9 +90,13 @@ describe('Index', function() {
|
|||||||
});
|
});
|
||||||
describe('#cacheLong', function() {
|
describe('#cacheLong', function() {
|
||||||
it('will set LONG cache control header', function(done) {
|
it('will set LONG cache control header', function(done) {
|
||||||
|
var node = {
|
||||||
|
log: sinon.stub()
|
||||||
|
};
|
||||||
var index = new InsightAPI({
|
var index = new InsightAPI({
|
||||||
enableCache: true,
|
enableCache: true,
|
||||||
cacheLongSeconds: 86400000
|
cacheLongSeconds: 86400000,
|
||||||
|
node: node
|
||||||
});
|
});
|
||||||
var req = {};
|
var req = {};
|
||||||
var res = {
|
var res = {
|
||||||
@ -91,8 +111,12 @@ describe('Index', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('will set LONG DEFAULT cache control header', function(done) {
|
it('will set LONG DEFAULT cache control header', function(done) {
|
||||||
|
var node = {
|
||||||
|
log: sinon.stub()
|
||||||
|
};
|
||||||
var index = new InsightAPI({
|
var index = new InsightAPI({
|
||||||
enableCache: true
|
enableCache: true,
|
||||||
|
node: node
|
||||||
});
|
});
|
||||||
var req = {};
|
var req = {};
|
||||||
var res = {
|
var res = {
|
||||||
|
|||||||
189
test/ratelimeter.js
Normal file
189
test/ratelimeter.js
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var should = require('should');
|
||||||
|
var sinon = require('sinon');
|
||||||
|
|
||||||
|
var RateLimiter = require('../lib/ratelimiter');
|
||||||
|
|
||||||
|
describe('RateLimiter', function() {
|
||||||
|
|
||||||
|
describe('@constructor', function() {
|
||||||
|
it('will instantiate without options', function() {
|
||||||
|
var limiter = new RateLimiter();
|
||||||
|
should.exist(limiter);
|
||||||
|
});
|
||||||
|
it('will instantiate without new', function() {
|
||||||
|
/* jshint newcap:false */
|
||||||
|
var limiter = RateLimiter();
|
||||||
|
should.exist(limiter);
|
||||||
|
});
|
||||||
|
it('will instantiate with options', function() {
|
||||||
|
var whitelist = [];
|
||||||
|
var blacklist = [];
|
||||||
|
var node = {};
|
||||||
|
var limiter = new RateLimiter({
|
||||||
|
node: node,
|
||||||
|
whitelist: whitelist,
|
||||||
|
blacklist: blacklist,
|
||||||
|
limit: 1,
|
||||||
|
interval: 1,
|
||||||
|
whitelistLimit: 1,
|
||||||
|
whitelistInterval: 1,
|
||||||
|
blacklistLimit: 1,
|
||||||
|
blacklistInterval: 1
|
||||||
|
});
|
||||||
|
should.exist(limiter);
|
||||||
|
should.exist(limiter.config);
|
||||||
|
should.exist(limiter.clients);
|
||||||
|
should.exist(limiter.node);
|
||||||
|
limiter.whitelist.should.equal(whitelist);
|
||||||
|
limiter.blacklist.should.equal(blacklist);
|
||||||
|
limiter.config.whitelist.totalRequests.should.equal(1);
|
||||||
|
limiter.config.whitelist.interval.should.equal(1);
|
||||||
|
limiter.config.blacklist.totalRequests.should.equal(1);
|
||||||
|
limiter.config.blacklist.interval.should.equal(1);
|
||||||
|
limiter.config.normal.interval.should.equal(1);
|
||||||
|
limiter.config.normal.totalRequests.should.equal(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#middleware', function() {
|
||||||
|
it('will set ratelimit headers', function(done) {
|
||||||
|
var limiter = new RateLimiter();
|
||||||
|
var req = {
|
||||||
|
headers: {
|
||||||
|
'cf-connecting-ip': '127.0.0.1'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var setHeader = sinon.stub();
|
||||||
|
var res = {
|
||||||
|
setHeader: setHeader
|
||||||
|
};
|
||||||
|
limiter.middleware()(req, res, function() {
|
||||||
|
setHeader.callCount.should.equal(2);
|
||||||
|
setHeader.args[0][0].should.equal('X-RateLimit-Limit');
|
||||||
|
setHeader.args[0][1].should.equal(10800);
|
||||||
|
setHeader.args[1][0].should.equal('X-RateLimit-Remaining');
|
||||||
|
setHeader.args[1][1].should.equal(10799);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('will give rate limit error', function() {
|
||||||
|
var node = {
|
||||||
|
log: {
|
||||||
|
warn: sinon.stub()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var limiter = new RateLimiter({node: node});
|
||||||
|
limiter.exceeded = sinon.stub().returns(true);
|
||||||
|
var req = {
|
||||||
|
headers: {
|
||||||
|
'cf-connecting-ip': '127.0.0.1'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var jsonp = sinon.stub();
|
||||||
|
var status = sinon.stub().returns({
|
||||||
|
jsonp: jsonp
|
||||||
|
});
|
||||||
|
var res = {
|
||||||
|
status: status,
|
||||||
|
setHeader: sinon.stub()
|
||||||
|
};
|
||||||
|
limiter.middleware()(req, res);
|
||||||
|
status.callCount.should.equal(1);
|
||||||
|
status.args[0][0].should.equal(429);
|
||||||
|
jsonp.callCount.should.equal(1);
|
||||||
|
jsonp.args[0][0].should.eql({
|
||||||
|
status: 429,
|
||||||
|
error: 'Rate limit exceeded'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#exceeded', function() {
|
||||||
|
it('should not be exceeded', function() {
|
||||||
|
var node = {};
|
||||||
|
var limiter = new RateLimiter({node: node});
|
||||||
|
var client = limiter.addClient('127.0.0.1');
|
||||||
|
var exceeded = limiter.exceeded(client);
|
||||||
|
exceeded.should.equal(false);
|
||||||
|
});
|
||||||
|
it('should be exceeded', function() {
|
||||||
|
var node = {};
|
||||||
|
var limiter = new RateLimiter({node: node});
|
||||||
|
var client = limiter.addClient('127.0.0.1');
|
||||||
|
client.visits = 3 * 60 * 60 + 1;
|
||||||
|
var exceeded = limiter.exceeded(client);
|
||||||
|
exceeded.should.equal(true);
|
||||||
|
});
|
||||||
|
it('should exclude whitelisted with no limit', function() {
|
||||||
|
var node = {};
|
||||||
|
var limiter = new RateLimiter({
|
||||||
|
whitelist: [
|
||||||
|
'127.0.0.1'
|
||||||
|
],
|
||||||
|
node: node,
|
||||||
|
whitelistLimit: -1
|
||||||
|
});
|
||||||
|
var client = limiter.addClient('127.0.0.1');
|
||||||
|
client.visits = Infinity;
|
||||||
|
var exceeded = limiter.exceeded(client);
|
||||||
|
exceeded.should.equal(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#getClientName', function() {
|
||||||
|
it('should get client name from cloudflare header', function() {
|
||||||
|
var node = {};
|
||||||
|
var limiter = new RateLimiter({node: node});
|
||||||
|
var req = {
|
||||||
|
headers: {
|
||||||
|
'cf-connecting-ip': '127.0.0.1'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var name = limiter.getClientName(req);
|
||||||
|
name.should.equal('127.0.0.1');
|
||||||
|
});
|
||||||
|
it('should get client name from x forwarded header', function() {
|
||||||
|
var node = {};
|
||||||
|
var limiter = new RateLimiter({node: node});
|
||||||
|
var req = {
|
||||||
|
headers: {
|
||||||
|
'x-forwarded-for': '127.0.0.1'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var name = limiter.getClientName(req);
|
||||||
|
name.should.equal('127.0.0.1');
|
||||||
|
});
|
||||||
|
it('should get client name from connection remote address', function() {
|
||||||
|
var node = {};
|
||||||
|
var limiter = new RateLimiter({node: node});
|
||||||
|
var req = {
|
||||||
|
headers: {},
|
||||||
|
connection: {
|
||||||
|
remoteAddress: '127.0.0.1'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var name = limiter.getClientName(req);
|
||||||
|
name.should.equal('127.0.0.1');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#addClient', function() {
|
||||||
|
var sandbox = sinon.sandbox.create();
|
||||||
|
afterEach(function() {
|
||||||
|
sandbox.restore();
|
||||||
|
});
|
||||||
|
it('will remove client after interval', function() {
|
||||||
|
var THREE_HOURS_PLUS = 3 * 60 * 60 * 1000 + 1;
|
||||||
|
var clock = sandbox.useFakeTimers();
|
||||||
|
var node = {};
|
||||||
|
var limiter = new RateLimiter({node: node});
|
||||||
|
limiter.addClient('127.0.0.1');
|
||||||
|
should.exist(limiter.clients['127.0.0.1']);
|
||||||
|
clock.tick(THREE_HOURS_PLUS);
|
||||||
|
should.not.exist(limiter.clients['127.0.0.1']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
@ -8,13 +8,13 @@ describe('Status', function() {
|
|||||||
describe('/status', function() {
|
describe('/status', function() {
|
||||||
var info = {
|
var info = {
|
||||||
version: 110000,
|
version: 110000,
|
||||||
protocolversion: 70002,
|
protocolVersion: 70002,
|
||||||
blocks: 548645,
|
blocks: 548645,
|
||||||
timeoffset: 0,
|
timeOffset: 0,
|
||||||
connections: 8,
|
connections: 8,
|
||||||
difficulty: 21546.906405522557,
|
difficulty: 21546.906405522557,
|
||||||
testnet: true,
|
testnet: true,
|
||||||
relayfee: 1000,
|
relayFee: 1000,
|
||||||
errors: ''
|
errors: ''
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -31,13 +31,9 @@ describe('Status', function() {
|
|||||||
var node = {
|
var node = {
|
||||||
services: {
|
services: {
|
||||||
bitcoind: {
|
bitcoind: {
|
||||||
getInfo: sinon.stub().returns(info),
|
getInfo: sinon.stub().callsArgWith(0, null, info),
|
||||||
getBestBlockHash: sinon.stub().returns(outSetInfo.bestblock)
|
getBestBlockHash: sinon.stub().callsArgWith(0, null, outSetInfo.bestblock),
|
||||||
},
|
tiphash: outSetInfo.bestblock
|
||||||
db: {
|
|
||||||
tip: {
|
|
||||||
hash: outSetInfo.bestblock
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -118,15 +114,10 @@ describe('Status', function() {
|
|||||||
it('should have correct data', function(done) {
|
it('should have correct data', function(done) {
|
||||||
var node = {
|
var node = {
|
||||||
services: {
|
services: {
|
||||||
db: {
|
|
||||||
tip: {
|
|
||||||
__height: 500000
|
|
||||||
}
|
|
||||||
},
|
|
||||||
bitcoind: {
|
bitcoind: {
|
||||||
height: 500000,
|
height: 500000,
|
||||||
isSynced: sinon.stub().returns(true),
|
isSynced: sinon.stub().callsArgWith(0, null, true),
|
||||||
syncPercentage: sinon.stub().returns(99.99)
|
syncPercentage: sinon.stub().callsArgWith(0, null, 99.99)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
1347
test/transactions.js
1347
test/transactions.js
File diff suppressed because it is too large
Load Diff
@ -10,12 +10,12 @@ describe('Utils', function() {
|
|||||||
var node = {
|
var node = {
|
||||||
services: {
|
services: {
|
||||||
bitcoind: {
|
bitcoind: {
|
||||||
estimateFee: function(blocks) {
|
estimateFee: function(blocks, callback) {
|
||||||
switch(blocks) {
|
switch(blocks) {
|
||||||
case 1:
|
case 1:
|
||||||
return 1000;
|
return callback(null, 1000 / 1e8);
|
||||||
case 3:
|
case 3:
|
||||||
return 3000;
|
return callback(null, 3000 / 1e8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user