bitcore node service

This commit is contained in:
Patrick Nagurny 2015-08-31 18:03:29 -04:00
parent 038eb401db
commit e4992a6dfb
9 changed files with 1040 additions and 0 deletions

213
bitcore-node/addresses.js Normal file
View File

@ -0,0 +1,213 @@
'use strict';
var common = require('./common');
var bitcore = require('bitcore');
var TxController = require('./transactions');
function AddressController(node) {
this.node = node;
this.txController = new TxController(node);
}
AddressController.prototype.show = function(req, res, next) {
this.getAddressHistory(req.addr, function(err, data) {
if(err) {
return common.handleErrors(err, res);
}
res.jsonp(data);
});
};
AddressController.prototype.balance = function(req, res, next) {
this.addressHistorySubQuery(req, res, next, 'balanceSat');
};
AddressController.prototype.totalReceived = function(req, res, next) {
this.addressHistorySubQuery(req, res, next, 'totalReceivedSat');
};
AddressController.prototype.totalSent = function(req, res, next) {
this.addressHistorySubQuery(req, res, next, 'totalSentSat');
};
AddressController.prototype.unconfirmedBalance = function(req, res, next) {
this.addressHistorySubQuery(req, res, next, 'unconfirmedBalanceSat');
};
AddressController.prototype.addressHistorySubQuery = function(req, res, next, param) {
this.getAddressHistory(req.addr, function(err, data) {
if(err) {
return common.handleErrors(err, res);
}
res.jsonp(data[param]);
});
};
AddressController.prototype.getAddressHistory = function(address, callback) {
var self = this;
this.node.getAddressHistory(address, true, function(err, txinfos) {
if(err) {
return callback(err);
}
callback(null, self.transformAddressHistory(txinfos, address));
});
};
AddressController.prototype.checkAddr = function(req, res, next) {
req.addr = req.params.addr;
this.check(req, res, next, [req.addr]);
};
AddressController.prototype.checkAddrs = function(req, res, next) {
if(req.body.addrs) {
req.addrs = req.body.addrs.split(',');
} else {
req.addrs = req.params.addrs.split(',');
}
this.check(req, res, next, req.addrs);
}
AddressController.prototype.check = function(req, res, next, addresses) {
if(!addresses.length || !addresses[0]) {
return common.handleErrors({
message: 'Must include address',
code: 1
}, res);
}
for(var i = 0; i < addresses.length; i++) {
try {
var a = new bitcore.Address(addresses[i]);
} catch(e) {
return common.handleErrors({
message: 'Invalid address: ' + e.message,
code: 1
}, res);
}
}
next();
}
AddressController.prototype.transformAddressHistory = function(txinfos, address) {
var transactions = txinfos.map(function(info) {
return info.tx.hash;
}).filter(function(value, index, self) {
return self.indexOf(value) === index;
});
var balance = 0;
var appearances = 0;
var totalReceived = 0;
var totalSent = 0;
var unconfirmedBalance = 0;
var unconfirmedAppearances = 0;
for(var i = 0; i < txinfos.length; i++) {
if(txinfos[i].satoshis > 0) {
totalReceived += txinfos[i].satoshis;
} else {
totalSent += -txinfos[i].satoshis;
}
if(txinfos[i].confirmations) {
balance += txinfos[i].satoshis;
unconfirmedBalance += txinfos[i].satoshis;
appearances++;
} else {
unconfirmedBalance += txinfos[i].satoshis;
unconfirmedAppearances++;
}
}
return {
addrStr: address,
balance: balance / 1e8,
balanceSat: balance,
totalReceived: totalReceived / 1e8,
totalReceivedSat: totalReceived,
totalSent: totalSent / 1e8,
totalSentSat: totalSent,
unconfirmedBalance: unconfirmedBalance / 1e8,
unconfirmedBalanceSat: unconfirmedBalance,
unconfirmedTxApperances: unconfirmedAppearances, // misspelling - ew
txApperances: appearances, // yuck
transactions: transactions
};
};
AddressController.prototype.utxo = function(req, res, next) {
var self = this;
this.node.getUnspentOutputs(req.addr, true, function(err, utxos) {
if(err && err instanceof self.node.errors.NoOutputs) {
return res.jsonp([]);
} else if(err) {
return common.handleErrors(err, res);
}
res.jsonp(utxos.map(self.transformUtxo.bind(self)));
});
};
AddressController.prototype.multiutxo = function(req, res, next) {
var self = this;
this.node.getUnspentOutputs(req.addrs, true, function(err, utxos) {
if(err && err instanceof self.node.errors.NoOutputs) {
return res.jsonp([]);
} else if(err) {
return common.handleErrors(err, res);
}
res.jsonp(utxos.map(self.transformUtxo.bind(self)));
});
};
AddressController.prototype.transformUtxo = function(utxo) {
return {
address: utxo.address,
txid: utxo.txid,
vout: utxo.outputIndex,
ts: utxo.timestamp ? Math.round(utxo.timestamp / 1000) : Math.round(Date.now() / 1000),
scriptPubKey: utxo.script,
amount: utxo.satoshis / 1e8,
confirmations: utxo.confirmations
};
};
AddressController.prototype.multitxs = function(req, res, next) {
var self = this;
this.node.getAddressHistory(req.addrs, true, function(err, txinfos) {
if(err) {
return common.handleErrors(err, res);
}
res.jsonp(self.transformAddressHistoryForMultiTxs(txinfos));
});
};
AddressController.prototype.transformAddressHistoryForMultiTxs = function(txinfos) {
var self = this;
var transformed = {
totalItems: txinfos.length,
from: 0,
to: txinfos.length,
items: txinfos.map(function(txinfo) {
return self.txController.transformTransaction(txinfo.tx);
}).reverse()
};
return transformed;
};
module.exports = AddressController;

128
bitcore-node/blocks.js Normal file
View File

@ -0,0 +1,128 @@
'use strict';
var common = require('./common');
var async = require('async');
var bitcore = require('bitcore');
var BufferUtil = bitcore.util.buffer;
function BlockController(node) {
this.node = node;
}
var BLOCK_LIMIT = 200;
/**
* Find block by hash ...
*/
BlockController.prototype.block = function(req, res, next, hash) {
var self = this;
this.node.getBlock(hash, function(err, block) {
if(err && err.message === 'Block not found.') {
// TODO libbitcoind should pass an instance of errors.Block.NotFound
return common.handleErrors(null, res);
} else if(err) {
return common.handleErrors(err, res);
}
var info = self.node.services.bitcoind.getBlockIndex(hash);
req.block = self.transformBlock(block, info);
next();
});
};
BlockController.prototype.transformBlock = function(block, info) {
var blockObj = block.toObject();
var transactionIds = blockObj.transactions.map(function(tx) {
return tx.hash
});
return {
hash: block.hash,
confirmations: this.node.services.db.tip.__height - info.height + 1,
size: block.toBuffer().length,
height: info.height,
version: blockObj.header.version,
merkleroot: blockObj.header.merkleRoot,
tx: transactionIds,
time: blockObj.header.time,
nonce: blockObj.header.nonce,
bits: blockObj.header.bits.toString(16),
difficulty: 0, // placeholder
chainwork: info.chainWork,
previousblockhash: blockObj.header.prevHash,
nextblockhash: null, // placeholder
reward: 0, // First output of first transaction gives us the reward + fees. How to isolate just reward?
isMainChain: true // placeholder
}
};
/**
* Show block
*/
BlockController.prototype.show = function(req, res) {
if (req.block) {
res.jsonp(req.block);
}
};
BlockController.prototype.blockIndex = function(req, res, next, height) {
var info = this.node.services.bitcoind.getBlockIndex(parseInt(height));
if(!info) {
return common.handleErrors(null, res);
}
res.jsonp({
blockHash: info.hash
});
};
// List blocks by date
BlockController.prototype.list = function(req, res) {
var self = this;
// TODO actually get blocks by date instead of just the last blocks
// TODO pagination
var limit = parseInt(req.query.limit || BLOCK_LIMIT);
var blocks = [];
var lastHash = this.node.services.db.tip.hash;
async.timesSeries(limit, function(n, next) {
self.node.getBlock(lastHash, function(err, block) {
if(err) {
return next(err);
}
var info = self.node.services.bitcoind.getBlockIndex(block.hash);
block.__height = info.height;
blocks.push(block);
lastHash = BufferUtil.reverse(block.header.prevHash).toString('hex');
next();
});
}, function(err) {
if(err) {
return common.handleErrors(err, res);
}
var data = {
blocks: blocks.map(function(block) {
return {
height: block.__height,
size: block.toBuffer().length,
hash: block.hash,
time: block.header.time,
txlength: block.transactions.length,
poolInfo: {}
};
}),
length: blocks.length,
pagination: {}
};
res.jsonp(data);
});
};
module.exports = BlockController;

19
bitcore-node/common.js Normal file
View File

@ -0,0 +1,19 @@
'use strict';
exports.notReady = function (err, res, p) {
res.status(503).send('Server not yet ready. Sync Percentage:' + p);
};
exports.handleErrors = function (err, res) {
if (err) {
if (err.code) {
res.status(400).send(err.message + '. Code:' + err.code);
}
else {
res.status(503).send(err.message);
}
}
else {
res.status(404).send('Not found');
}
};

97
bitcore-node/index.js Normal file
View File

@ -0,0 +1,97 @@
'use strict';
var BaseService = require('./service');
var inherits = require('util').inherits;
var BlockController = require('./blocks');
var TxController = require('./transactions');
var AddressController = require('./addresses');
var StatusController = require('./status');
var InsightAPI = function(options) {
BaseService.call(this, options);
};
InsightAPI.dependencies = ['address', 'web'];
inherits(InsightAPI, BaseService);
InsightAPI.prototype.setupRoutes = function(app) {
var apiPrefix = '/api';
//Block routes
var blocks = new BlockController(this.node);
app.get(apiPrefix + '/blocks', blocks.list.bind(blocks));
app.get(apiPrefix + '/block/:blockHash', blocks.show.bind(blocks));
app.param('blockHash', blocks.block.bind(blocks));
app.get(apiPrefix + '/block-index/:height', blocks.blockIndex.bind(blocks));
app.param('height', blocks.blockIndex.bind(blocks));
// Transaction routes
var transactions = new TxController(this.node);
app.get(apiPrefix + '/tx/:txid', transactions.show.bind(transactions));
app.param('txid', transactions.transaction.bind(transactions));
app.get(apiPrefix + '/txs', transactions.list.bind(transactions));
app.post(apiPrefix + '/tx/send', transactions.send.bind(transactions));
// Raw Routes
app.get(apiPrefix + '/rawtx/:txid', transactions.showRaw.bind(transactions));
app.param('txid', transactions.rawTransaction.bind(transactions));
// Address routes
var addresses = new AddressController(this.node);
app.get(apiPrefix + '/addr/:addr', addresses.checkAddr.bind(addresses), addresses.show.bind(addresses));
app.get(apiPrefix + '/addr/:addr/utxo', addresses.checkAddr.bind(addresses), addresses.utxo.bind(addresses));
app.get(apiPrefix + '/addrs/:addrs/utxo', addresses.checkAddrs.bind(addresses), addresses.multiutxo.bind(addresses));
app.post(apiPrefix + '/addrs/utxo', addresses.checkAddrs.bind(addresses), addresses.multiutxo.bind(addresses));
app.get(apiPrefix + '/addrs/:addrs/txs', addresses.checkAddrs.bind(addresses), addresses.multitxs.bind(addresses));
app.post(apiPrefix + '/addrs/txs', addresses.checkAddrs.bind(addresses), addresses.multitxs.bind(addresses));
// Address property routes
app.get(apiPrefix + '/addr/:addr/balance', addresses.checkAddr.bind(addresses), addresses.balance.bind(addresses));
app.get(apiPrefix + '/addr/:addr/totalReceived', addresses.checkAddr.bind(addresses), addresses.totalReceived.bind(addresses));
app.get(apiPrefix + '/addr/:addr/totalSent', addresses.checkAddr.bind(addresses), addresses.totalSent.bind(addresses));
app.get(apiPrefix + '/addr/:addr/unconfirmedBalance', addresses.checkAddr.bind(addresses), addresses.unconfirmedBalance.bind(addresses));
// Status route
var status = new StatusController(this.node);
app.get(apiPrefix + '/status', status.show.bind(status));
app.get(apiPrefix + '/sync', status.sync.bind(status));
app.get(apiPrefix + '/peer', status.peer.bind(status));
app.get(apiPrefix + '/version', status.version.bind(status));
// Utils route
/*var utils = require('../app/controllers/utils');
app.get(apiPrefix + '/utils/estimatefee', utils.estimateFee);
// Currency
var currency = require('../app/controllers/currency');
app.get(apiPrefix + '/currency', currency.index);
// Email store plugin
if (config.enableEmailstore) {
var emailPlugin = require('../plugins/emailstore');
app.get(apiPrefix + '/email/retrieve', emailPlugin.retrieve);
}
// Currency rates plugin
if (config.enableCurrencyRates) {
var currencyRatesPlugin = require('../plugins/currencyrates');
app.get(apiPrefix + '/rates/:code', currencyRatesPlugin.getRate);
}
// Address routes
var messages = require('../app/controllers/messages');
app.get(apiPrefix + '/messages/verify', messages.verify);
app.post(apiPrefix + '/messages/verify', messages.verify);
//Home route
var index = require('../app/controllers/index');
app.get('*', index.render);*/
};
module.exports = InsightAPI;

91
bitcore-node/service.js Normal file
View File

@ -0,0 +1,91 @@
'use strict';
var util = require('util');
var EventEmitter = require('events').EventEmitter;
var Service = function(options) {
EventEmitter.call(this);
this.node = options.node;
this.name = options.name;
};
util.inherits(Service, EventEmitter);
/**
* Describes the dependencies that should be loaded before this service.
*/
Service.dependencies = [];
/**
* blockHandler
* @param {Block} block - the block being added or removed from the chain
* @param {Boolean} add - whether the block is being added or removed
* @param {Function} callback - call with the leveldb database operations to perform
*/
Service.prototype.blockHandler = function(block, add, callback) {
// implement in the child class
setImmediate(function() {
callback(null, []);
});
};
/**
* the bus events available for subscription
* @return {Array} an array of event info
*/
Service.prototype.getPublishEvents = function() {
// Example:
// return [
// ['eventname', this, this.subscribeEvent, this.unsubscribeEvent],
// ];
return [];
};
/**
* the API methods to expose
* @return {Array} return array of methods
*/
Service.prototype.getAPIMethods = function() {
// Example:
// return [
// ['getData', this, this.getData, 1]
// ];
return [];
};
// Example:
// Service.prototype.getData = function(arg1, callback) {
//
// };
/**
* Function which is called when module is first initialized
*/
Service.prototype.start = function(done) {
setImmediate(done);
};
/**
* Function to be called when bitcore-node is stopped
*/
Service.prototype.stop = function(done) {
setImmediate(done);
};
/**
* Setup express routes
* @param {Express} app
*/
Service.prototype.setupRoutes = function(app) {
// Setup express routes here
};
Service.prototype.getRoutePrefix = function() {
return this.name;
};
module.exports = Service;

75
bitcore-node/status.js Normal file
View File

@ -0,0 +1,75 @@
'use strict';
function StatusController(node) {
this.node = node;
}
StatusController.prototype.show = function(req, res) {
var option = req.query.q;
switch(option) {
case 'getDifficulty':
res.jsonp(this.getDifficulty());
break;
case 'getTxOutSetInfo':
// TODO
// break;
case 'getLastBlockHash':
// TODO
// break;
case 'getBestBlockHash':
// TODO
// break;
case 'getInfo':
default:
res.jsonp(this.getInfo());
}
};
StatusController.prototype.getInfo = function() {
return this.node.services.bitcoind.getInfo();
};
StatusController.prototype.getDifficulty = function() {
var info = this.node.services.bitcoind.getInfo();
return {
difficulty: info.difficulty
};
};
StatusController.prototype.sync = function(req, res) {
var status = 'syncing';
if(this.node.services.bitcoind.isSynced() && this.node.services.db.tip.__height === this.node.services.bitcoind.height) {
status = 'finished';
}
var info = {
status: status,
blockChainHeight: this.node.services.bitcoind.height,
syncPercentage: this.node.services.db.tip.__height / this.node.services.bitcoind.height * 100,
height: this.node.services.db.tip.__height,
error: null,
type: 'bitcore node'
};
res.jsonp(info);
};
// Hard coded to make insight ui happy, but not applicable
StatusController.prototype.peer = function(req, res) {
res.jsonp({
connected: true,
host: '127.0.0.1',
port: null
});
};
StatusController.prototype.version = function(req, res) {
var pjson = require('../../../package.json');
res.json({
version: pjson.version
});
}
module.exports = StatusController;

View File

@ -0,0 +1,251 @@
'use strict';
var bitcore = require('bitcore');
var common = require('./common');
var async = require('async');
function TxController(node) {
this.node = node;
}
TxController.prototype.show = function(req, res) {
if (req.transaction) {
res.jsonp(req.transaction);
}
};
/**
* Find transaction by hash ...
*/
TxController.prototype.transaction = function(req, res, next, txid) {
var self = this;
this.node.getTransactionWithBlockInfo(txid, true, function(err, transaction) {
if (err && err instanceof self.node.errors.Transaction.NotFound) {
return common.handleErrors(null, res);
} else if(err) {
return common.handleErrors(err, res);
}
transaction.populateInputs(self.node.services.db, [], function(err) {
if(err) {
return res.send({
error: err.toString()
})
}
req.transaction = self.transformTransaction(transaction);
next();
});
});
};
TxController.prototype.transformTransaction = function(transaction) {
var txObj = transaction.toObject();
var confirmations = 0;
if(transaction.__height >= 0) {
confirmations = this.node.services.db.tip.__height - transaction.__height + 1;
}
var transformed = {
txid: txObj.hash,
version: txObj.version,
locktime: txObj.nLockTime
};
if(transaction.isCoinbase()) {
transformed.vin = [
{
coinbase: txObj.inputs[0].script,
sequence: txObj.inputs[0].sequenceNumber,
n: 0
}
];
} else {
transformed.vin = txObj.inputs.map(this.transformInput.bind(this));
}
transformed.vout = txObj.outputs.map(this.transformOutput.bind(this));
transformed.blockhash = transaction.__blockHash;
transformed.confirmations = confirmations;
transformed.time = transaction.__timestamp ? transaction.__timestamp : Date.now(); // can we get this from bitcoind?
transformed.blocktime = transformed.time;
if(transaction.isCoinbase()) {
transformed.isCoinBase = true;
}
transformed.valueOut = transaction.outputAmount / 1e8;
transformed.size = transaction.toBuffer().length;
if(transaction.hasAllUtxoInfo()) {
transformed.valueIn = transaction.inputAmount / 1e8;
transformed.fees = transaction.getFee() / 1e8;
}
return transformed;
};
TxController.prototype.transformInput = function(input, index) {
var transformed = {
txid: input.prevTxId,
vout: input.outputIndex,
scriptSig: {
asm: null, // TODO
hex: input.script
},
sequence: input.sequenceNumber,
n: index
};
if(input.output) {
transformed.addr = bitcore.Script(input.output.script).toAddress(this.node.network).toString();
transformed.valueSat = input.output.satoshis;
transformed.value = input.output.satoshis / 1e8;
transformed.doubleSpentTxID = null; // TODO
transformed.isConfirmed = null; // TODO
transformed.confirmations = null; // TODO
transformed.unconfirmedInput = null; // TODO
}
return transformed;
};
TxController.prototype.transformOutput = function(output, index) {
var transformed = {
value: (output.satoshis / 1e8).toFixed(8),
n: index,
scriptPubKey: {
asm: null, // TODO
hex: output.script,
reqSigs: null, // TODO
type: null // TODO
},
spentTxId: null, // TODO
spentIndex: null, // TODO
spentTs: null // TODO
};
var address = bitcore.Script(output.script).toAddress(this.node.network).toString();
if(address !== 'false') {
transformed.scriptPubKey.addresses = [address];
}
return transformed;
};
TxController.prototype.rawTransaction = function(req, res, next, txid) {
this.node.getTransaction(txid, true, function(err, transaction) {
if (err && err instanceof self.node.errors.Transaction.NotFound) {
return common.handleErrors(null, res);
} else if(err) {
return common.handleErrors(err, res);
}
req.rawTransaction = {
'rawtx': transaction.toBuffer().toString('hex')
};
next();
});
};
TxController.prototype.showRaw = function(req, res) {
if (req.rawTransaction) {
res.jsonp(req.rawTransaction);
}
};
TxController.prototype.list = function(req, res) {
var self = this;
var blockHash = req.query.block;
var address = req.query.address;
var page = req.query.pageNum;
var pageLength = 10;
var pagesTotal = 1;
if(blockHash) {
self.node.getBlock(blockHash, function(err, block) {
if(err && err.message === 'Block not found.') {
return common.handleErrors(null, res);
} else if(err) {
return common.handleErrors(err, res);
}
var blockInfo = self.node.services.bitcoind.getBlockIndex(block.hash);
var txs = block.transactions;
var totalTxs = txs.length;
if(page) {
txs = txs.splice(page * pageLength, pageLength);
pagesTotal = Math.ceil(totalTxs / pageLength);
}
async.mapSeries(txs, function(tx, next) {
tx.__blockHash = block.hash;
tx.__height = blockInfo.height;
tx.__timestamp = block.header.time;
tx.populateInputs(self.node.services.db, [], function(err) {
if(err) {
return next(err);
}
next(null, self.transformTransaction(tx));
});
}, function(err, transformed) {
if(err) {
return common.handleErrors(err, res);
}
res.jsonp({
pagesTotal: pagesTotal,
txs: transformed
});
});
});
} else if(address) {
self.node.getAddressHistory(address, true, function(err, txinfos) {
if(err) {
return common.handleErrors(err, res);
}
var txs = txinfos.map(function(info) {
return info.tx;
}).filter(function(value, index, self) {
return self.indexOf(value) === index;
});
var totalTxs = txs.length;
if(page) {
txs = txs.splice(page * pageLength, pageLength);
pagesTotal = Math.ceil(totalTxs / pageLength);
}
var transformed = txs.map(self.transformTransaction.bind(self));
res.jsonp({
pagesTotal: pagesTotal,
txs: transformed
});
});
} else {
return common.handleErrors(new Error('Block hash or address expected'), res);
}
};
TxController.prototype.send = function(req, res) {
this.node.sendTransaction(req.body.rawtx, function(err, txid) {
if(err) {
// TODO handle specific errors
return common.handleErrors(err, res);
}
res.json({'txid': txid});
});
};
module.exports = TxController;

View File

@ -43,6 +43,7 @@
"scripts": {
"start": "node insight.js"
},
"bitcoreNode": "bitcore-node",
"dependencies": {
"async": "*",
"base58-native": "0.1.2",

165
test/bitcore-node/blocks.js Normal file
View File

@ -0,0 +1,165 @@
'use strict';
var should = require('should');
var sinon = require('sinon');
var BlockController = require('../../bitcore-node/blocks');
var bitcore = require('bitcore');
var _ = require('lodash');
var blocks = {
'0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7': '07000020a491892cca9f143f7f00b8d65bbce0204bb32e17e914325fa5010000000000003e28f0519ecf01f7f01ea8da61084b2e4741a18ce1f3738117b84458353764b06fb9e35567f20c1a78eb626f0301000000010000000000000000000000000000000000000000000000000000000000000000ffffffff2303d6250800feb0aae355fe263600000963676d696e6572343208ae5800000000000000ffffffff01c018824a000000001976a91468bedce8982d25c3b6b03f6238cbad00378b8ead88ac000000000100000002ad5a14ae9d0f3221b790c4fc590fddceea1456e5692d8c4bf1ff7175f2b0c987000000008b4830450221008e5df62719cd92d7b137d00bbd27f153f2909bcad3a300960bc1020ec6d5e961022039df51600ff4fb5da5a794d1648c6b47c1f7d277fd5877fb5e52a730a3595f8c014104eb1e0ccd9afcac42229348dd776e991c69551ae3474340fada12e787e51758397e1d3afdba360d6374261125ea3b6ea079a5f202c150dfd729e1062d9176a307ffffffff9621ac65bc22ea593ca9a61a8d63e461bf3d3f277989df5d3bd33ddfae0aa1d8000000008a4730440220761464d7bab9515d92260762a97af82a9b25d202d8f7197b1aaec81b6fed541f022059f99606de6b06e17b2cd102dceb3807ebdd9e777a5b77c9a0b3672f5eabcb31014104eb1e0ccd9afcac42229348dd776e991c69551ae3474340fada12e787e51758397e1d3afdba360d6374261125ea3b6ea079a5f202c150dfd729e1062d9176a307ffffffff02dc374401000000001976a9144b7b335f978f130269fe661423258ae9642df8a188ac72b3d000000000001976a9146efcf883b4b6f9997be9a0600f6c095fe2bd2d9288ac000000000100000002060d3cb6dfb7ffe85e2908010fea63190c9707e96fc7448128eb895b5e222771030000006b483045022100f67cffc0ae23adb236ff3edb4a9736e277605db30cc7708dfab8cf1e1483bbce022052396aa5d664ec1cb65992c423fd9a17e94dc7af328d2d559e90746dd195ca5901210346134da14907581d8190d3980caaf46d95e4eb9c1ca8e70f1fc6007fefb1909dfeffffff7b2d8a8263cffbdb722e2a5c74166e6f2258634e277c0b08f51b578b667e2fba000000006a473044022077222a91cda23af69179377c62d84a176fb12caff6c5cbf6ae9e5957ff3b1afe0220768edead76819228dcba18cca3c9a5a5d4c32919720f21df21a297ba375bbe5c012103371ea5a4dfe356b3ea4042a537d7ab7ee0faabd43e21b6cc076fda2240629eeefeffffff02209a1d00000000001976a9148e451eec7ca0a1764b4ab119274efdd2727b3c8588ac40420f00000000001976a914d0fce8f064cd1059a6a11501dd66fe42368572b088accb250800',
'000000000008fbb2e358e382a6f6948b2da24563bba183af447e6e2542e8efc7': '0300000041c49521c6106debe1221077ca9e71850e3c328037e5e98fbd0600000000000082b3a56da828618b1b7b103763e3c21c38c9536ce185ddd4aebdcd627645a878fb96e355ffff001d192907730101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff2803bf25081b4d696e656420627920416e74506f6f6c202bde53072055e396fb010300000099050000ffffffff01807c814a000000001976a9144c5220c58749e5474ec4e5bbb0c2cc0342c6f44488ac00000000',
'00000000000006bd8fe9e53780323c0e85719eca771022e1eb6d10c62195c441': '030000004705096ec15ee742e9c14aee22f5f30cb568a0817b4a6c1ec4b9010000000000afb4442dbd55dcc8c86c94dc84ca0d130aeade144623fc0e47849343087067204792e35567f20c1a4b773d0f0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff2803be25081b4d696e656420627920416e74506f6f6c202bde53072055e392470100000000aba10000ffffffff01807c814a000000001976a9144c5220c58749e5474ec4e5bbb0c2cc0342c6f44488ac00000000'
};
var blockIndexes = {
'0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7': {
hash: '0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7',
chainWork: '0000000000000000000000000000000000000000000000054626b1839ade284a',
prevHash: '00000000000001a55f3214e9172eb34b20e0bc5bd6b8007f3f149fca2c8991a4',
height: 533974
},
'000000000008fbb2e358e382a6f6948b2da24563bba183af447e6e2542e8efc7': {
hash: '000000000008fbb2e358e382a6f6948b2da24563bba183af447e6e2542e8efc7',
chainWork: '00000000000000000000000000000000000000000000000544ea52e1575ca753',
prevHash: '00000000000006bd8fe9e53780323c0e85719eca771022e1eb6d10c62195c441',
height: 533951
},
'00000000000006bd8fe9e53780323c0e85719eca771022e1eb6d10c62195c441': {
hash: '00000000000006bd8fe9e53780323c0e85719eca771022e1eb6d10c62195c441',
chainWork: '00000000000000000000000000000000000000000000000544ea52e0575ba752',
prevHash: '000000000001b9c41e6c4a7b81a068b50cf3f522ee4ac1e942e75ec16e090547',
height: 533950
}
};
describe('Blocks', function() {
describe('/blocks/:blockHash route', function() {
var insight = {
"hash": "0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7",
"confirmations": 119,
"size": 1011,
"height": 533974,
"version": 536870919,
"merkleroot": "b06437355844b8178173f3e18ca141472e4b0861daa81ef0f701cf9e51f0283e",
"tx": [
"25a988e54b02e0e5df146a0f8fa7b9db56210533a9f04bdfda5f4ceb6f77aadd",
"b85334bf2df35c6dd5b294efe92ffc793a78edff75a2ca666fc296ffb04bbba0",
"2e01c7a4a0e335112236b711c4aaddd02e8dc59ba2cda416e8f80ff06dddd7e1"
],
"time": 1440987503,
"nonce": 1868753784,
"bits": "1a0cf267",
"difficulty": 1295829.93087696,
"chainwork": "0000000000000000000000000000000000000000000000054626b1839ade284a",
"previousblockhash": "00000000000001a55f3214e9172eb34b20e0bc5bd6b8007f3f149fca2c8991a4",
"nextblockhash": "000000000001e866a8057cde0c650796cb8a59e0e6038dc31c69d7ca6649627d",
"reward": 12.5,
"isMainChain": true
};
var bitcoreBlock = bitcore.Block.fromBuffer(new Buffer(blocks['0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7'], 'hex'));
var todos = {
difficulty: 1295829.93087696,
nextblockhash: '000000000001e866a8057cde0c650796cb8a59e0e6038dc31c69d7ca6649627d',
reward: 12.5
};
var node = {
getBlock: sinon.stub().callsArgWith(1, null, bitcoreBlock),
services: {
bitcoind: {
getBlockIndex: sinon.stub().returns(blockIndexes['0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7'])
},
db: {
tip: {
__height: 534092
}
}
}
};
it('block data should be correct', function(done) {
var blocks = new BlockController(node);
var req = {};
var res = {};
var next = function() {
should.exist(req.block);
var block = _.extend(req.block, todos);
should(block).eql(insight);
done();
};
var hash = '0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7';
blocks.block(req, res, next, hash);
});
});
describe('/blocks route', function() {
var insight = {
"blocks": [
{
"height": 533951,
"size": 206,
"hash": "000000000008fbb2e358e382a6f6948b2da24563bba183af447e6e2542e8efc7",
"time": 1440978683,
"txlength": 1,
"poolInfo": {}
},
{
"height": 533950,
"size": 206,
"hash": "00000000000006bd8fe9e53780323c0e85719eca771022e1eb6d10c62195c441",
"time": 1440977479,
"txlength": 1,
"poolInfo": {}
}
],
"length": 2,
"pagination": {}
};
var stub = sinon.stub();
stub.onFirstCall().callsArgWith(1, null, bitcore.Block.fromBuffer(blocks['000000000008fbb2e358e382a6f6948b2da24563bba183af447e6e2542e8efc7'], 'hex'));
stub.onSecondCall().callsArgWith(1, null, bitcore.Block.fromBuffer(blocks['00000000000006bd8fe9e53780323c0e85719eca771022e1eb6d10c62195c441'], 'hex'))
var node = {
getBlock: stub,
services: {
bitcoind: {
getBlockIndex: function(hash) {
return blockIndexes[hash];
}
},
db: {
tip: {
hash: '000000000008fbb2e358e382a6f6948b2da24563bba183af447e6e2542e8efc7'
}
}
}
};
it('should have correct data', function(done) {
var blocks = new BlockController(node);
var req = {
query: {
limit: 2
}
};
var res = {
jsonp: function(data) {
should(data).eql(insight);
done();
}
};
blocks.list(req, res);
});
});
});