diff --git a/README.md b/README.md index 4f300fc63..40f58f8fa 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Insight API +# Insight API [![Build Status](https://travis-ci.org/CoinSpace/insight-api.svg)](https://travis-ci.org/CoinSpace/insight-api) A Bitcoin blockchain REST and web socket API service for [Bitcore Node](https://github.com/bitpay/bitcore-node). @@ -53,6 +53,11 @@ Caching support has not yet been added in the v0.3 upgrade. /insight-api/block/00000000a967199a2fad0877433c93df785a8d8ce062e5f9b451cd1397bdbf62 ``` +#### Multiple blocks +``` + /insight-api/block/[:hash1],[:hash2],...,[:hash,] +``` + ### Block Index Get block hash by height ``` @@ -73,12 +78,24 @@ which is the hash of the Genesis block (0 height) /insight-api/rawtx/525de308971eabd941b139f46c7198b5af9479325c2395db7f2fb5ae8562556c ``` +### Multiple Transactions +``` + /insight-api/txs/[:rawid1],[:rawid2],...,[:rawidn] + /insight-api/rawtxs/[:rawid1],[:rawid2],...,[:rawidn] +``` + ### Address ``` /insight-api/addr/[:addr][?noTxList=1&noCache=1] /insight-api/addr/mmvP3mTe53qxHdPqXEvdu8WdC7GfQ2vmx5?noTxList=1 ``` +### Multiple Addresses +``` + /insight-api/addrs/[:addr1],[:addr2],...,[:addrn][?noTxList=1&noCache=1] + +``` + ### Address Properties ``` /insight-api/addr/[:addr]/balance diff --git a/lib/addresses.js b/lib/addresses.js index 765faab84..24783ff1e 100644 --- a/lib/addresses.js +++ b/lib/addresses.js @@ -24,6 +24,23 @@ AddressController.prototype.show = function(req, res) { }); }; +AddressController.prototype.multishow = function(req, res) { + var options = { + noTxList: parseInt(req.query.noTxList) + }; + + var self = this; + + async.map(req.addrs, function(addr, callback) { + self.getAddressSummary(addr, options, callback); + }, function(err, datas) { + if(err) { + return common.handleErrors(err, res); + } + res.jsonp(datas); + }); +}; + AddressController.prototype.balance = function(req, res) { this.addressSummarySubQuery(req, res, 'balanceSat'); }; @@ -51,8 +68,6 @@ AddressController.prototype.addressSummarySubQuery = function(req, res, param) { }; AddressController.prototype.getAddressSummary = function(address, options, callback) { - var self = this; - this.node.getAddressSummary(address, options, function(err, summary) { if(err) { return callback(err); @@ -90,7 +105,7 @@ AddressController.prototype.checkAddrs = function(req, res, next) { } this.check(req, res, next, req.addrs); -} +}; AddressController.prototype.check = function(req, res, next, addresses) { if(!addresses.length || !addresses[0]) { diff --git a/lib/blocks.js b/lib/blocks.js index 11806bda1..6501e1332 100644 --- a/lib/blocks.js +++ b/lib/blocks.js @@ -1,10 +1,11 @@ 'use strict'; -var common = require('./common'); var async = require('async'); +var batch = require('./common').batch; var bitcore = require('bitcore-lib'); -var pools = require('../pools.json'); var BN = bitcore.crypto.BN; +var common = require('./common'); +var pools = require('../pools.json'); function BlockController(node) { var self = this; @@ -26,24 +27,19 @@ var BLOCK_LIMIT = 200; /** * Find block by hash ... */ -BlockController.prototype.block = function(req, res, next, hash) { +BlockController.prototype.block = batch(function(hash, callback) { 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); + if (err) { + return callback(err); } var info = self.node.services.bitcoind.getBlockIndex(hash); info.isMainChain = self.node.services.bitcoind.isMainChain(hash); - req.block = self.transformBlock(block, info); - next(); + callback(null, self.transformBlock(block, info)); }); -}; +}, 'block'); BlockController.prototype.transformBlock = function(block, info) { var blockObj = block.toObject(); diff --git a/lib/common.js b/lib/common.js index d7fb9af67..a96f538a3 100644 --- a/lib/common.js +++ b/lib/common.js @@ -1,5 +1,7 @@ 'use strict'; +var async = require('async'); + exports.notReady = function (err, res, p) { res.status(503).send('Server not yet ready. Sync Percentage:' + p); }; @@ -17,3 +19,32 @@ exports.handleErrors = function (err, res) { res.status(404).send('Not found'); } }; + +/** + * batch + * + * @param handler + * @param outkey + * @returns {Function} + */ +exports.batch = function(handler, outkey) { + return function(req, res, next, inputdata) { + var self = this; + + async.mapSeries(inputdata.split(','), async.ensureAsync(function() { + return handler.apply(self, arguments); + }), function(err, results) { + if (err) { + return exports.handleErrors(err, res); + } + + if (results.length === 1) { + req[outkey] = results[0] + } else { + req[outkey] = results; + } + + return next(); + }); + }; +}; diff --git a/lib/index.js b/lib/index.js index 66c88ddfd..ec8393e34 100644 --- a/lib/index.js +++ b/lib/index.js @@ -108,16 +108,21 @@ InsightAPI.prototype.setupRoutes = function(app) { var transactions = new TxController(this.node); app.get('/tx/:txid', this.cacheLong(), transactions.show.bind(transactions)); app.param('txid', transactions.transaction.bind(transactions)); + app.get('/txs/:txids', this.cacheShort(), transactions.show.bind(transactions)); + app.param('txids', transactions.multitransactions.bind(transactions)); app.get('/txs', this.cacheShort(), transactions.list.bind(transactions)); app.post('/tx/send', transactions.send.bind(transactions)); // Raw Routes app.get('/rawtx/:txid', this.cacheLong(), transactions.showRaw.bind(transactions)); app.param('txid', transactions.rawTransaction.bind(transactions)); + app.get('/rawtxs/:txids', this.cacheLong(), transactions.showRaw.bind(transactions)); + app.param('txids', transactions.multiRawTransaction.bind(transactions)); // Address routes var addresses = new AddressController(this.node); app.get('/addr/:addr', this.cacheShort(), addresses.checkAddr.bind(addresses), addresses.show.bind(addresses)); + app.get('/addrs/:addrs', this.cacheShort(), addresses.checkAddrs.bind(addresses), addresses.multishow.bind(addresses)); app.get('/addr/:addr/utxo', this.cacheShort(), addresses.checkAddr.bind(addresses), addresses.utxo.bind(addresses)); app.get('/addrs/:addrs/utxo', this.cacheShort(), addresses.checkAddrs.bind(addresses), addresses.multiutxo.bind(addresses)); app.post('/addrs/utxo', this.cacheShort(), addresses.checkAddrs.bind(addresses), addresses.multiutxo.bind(addresses)); diff --git a/lib/transactions.js b/lib/transactions.js index d0a377b7a..097f108eb 100644 --- a/lib/transactions.js +++ b/lib/transactions.js @@ -1,10 +1,11 @@ 'use strict'; +var async = require('async'); var bitcore = require('bitcore-lib'); -var _ = bitcore.deps._; -var $ = bitcore.util.preconditions; var common = require('./common'); -var async = require('async'); + +var $ = bitcore.util.preconditions; +var _ = bitcore.deps._; function TxController(node) { this.node = node; @@ -48,6 +49,42 @@ TxController.prototype.transaction = function(req, res, next, txid) { }); }; +TxController.prototype.multitransactions = function(req, res, next, txids) { + var self = this; + + async.map(txids.split(','), function(txid, callback) { + self.node.getTransactionWithBlockInfo(txid, true, function(err, transaction) { + if (err) { + return callback(err); + } + + transaction.populateInputs(self.node.services.db, [], function(err) { + if (err) { + return callback(err); + } + + self.transformTransaction(transaction, function(err, transformedTransaction) { + if (err) { + return callback(err); + } + + callback(null, transformedTransaction); + }); + }); + }); + }, function(err, txs) { + if (err && err instanceof self.node.errors.Transaction.NotFound) { + return common.handleErrors(null, res); + } else if(err) { + return common.handleErrors(err, res); + } + + req.transaction = txs; + next(); + }); +}; + + TxController.prototype.transformTransaction = function(transaction, callback) { $.checkArgument(_.isFunction(callback)); var self = this; @@ -92,6 +129,7 @@ TxController.prototype.transformTransaction = function(transaction, callback) { transformed.vout = vout; transformed.blockhash = transaction.__blockHash; + transformed.blockheight = transaction.__height; transformed.confirmations = confirmations; var time = transaction.__timestamp ? transaction.__timestamp : Math.round(Date.now() / 1000); transformed.time = time; @@ -243,6 +281,28 @@ TxController.prototype.rawTransaction = function(req, res, next, txid) { }); }; +TxController.prototype.multiRawTransaction = function(req, res, next, txids) { + var self = this; + + async.map(txids.split(','), function(txid, callback) { + self.node.getTransaction(txid, true, callback); + }, function(err, txs) { + 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': txs.map(function(tx) { + return tx.toBuffer().toString('hex'); + }) + }; + + next(); + }); +}; + TxController.prototype.showRaw = function(req, res) { if (req.rawTransaction) { res.jsonp(req.rawTransaction); diff --git a/package.json b/package.json index b5c2b9a57..f137af33b 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,10 @@ { "name": "Braydon Fuller", "email": "braydon@bitpay.com" + }, + { + "name": "Eugene Krevenets", + "email": "ievgenii.krevenets@gmail.com" } ], "bugs": { @@ -70,6 +74,7 @@ "chai": "*", "mocha": "~1.16.2", "proxyquire": "^1.7.2", + "rewire": "^2.5.1", "should": "^2.1.1", "sinon": "^1.10.3" } diff --git a/test/addresses.js b/test/addresses.js index d86682d58..d526d7f51 100644 --- a/test/addresses.js +++ b/test/addresses.js @@ -274,6 +274,103 @@ describe('Addresses', function() { }); }); + describe('/addrs/:addrs', function() { + var addresses; + var node; + var req; + var summaries; + + beforeEach(function() { + var stub = sinon.stub(); + + summaries = [{ + balance: 0, + totalReceived: 2782729129, + totalSpent: 2782729129, + unconfirmedBalance: 0, + appearances: 2, + unconfirmedAppearances: 0, + txids: [ + 'bb0ec3b96209fac9529570ea6f83a86af2cceedde4aaf2bfcc4796680d23f1c7', + '01f700df84c466f2a389440e5eeacdc47d04f380c39e5d19dce2ce91a11ecba3' + ] + }, { + balance: 0, + totalReceived: 2782729129, + totalSpent: 2782729129, + unconfirmedBalance: 0, + appearances: 2, + unconfirmedAppearances: 0, + txids: [ + 'bb0ec3b96209fac9529570ea6f83a86af2cceedde4aaf2bfcc4796680d23f1c7', + '01f700df84c466f2a389440e5eeacdc47d04f380c39e5d19dce2ce91a11ecba3' + ] + }]; + + stub + .onFirstCall() + .callsArgWith(2, null, summaries[0]); + stub + .onSecondCall() + .callsArgWith(2, null, summaries[1]); + + node = { + getAddressSummary: stub + }; + + addresses = new AddressController(node); + + req = { + addrs: ['mzkD4nmQ8ixqxySdBgsXTpgvAMK5iRZpNK','moZY18rGNmh4YCPeugtGW46AkkWMQttBUD'], + query: {} + }; + }); + + it('should have correct data', function(done) { + addresses.multishow(req, { + jsonp: function(data) { + should(data).eql([ + { + addrStr: 'mzkD4nmQ8ixqxySdBgsXTpgvAMK5iRZpNK', + balance: 0, + balanceSat: 0, + totalReceived: 27.82729129, + totalReceivedSat: 2782729129, + totalSent: 27.82729129, + totalSentSat: 2782729129, + unconfirmedBalance: 0, + unconfirmedBalanceSat: 0, + unconfirmedTxApperances: 0, + txApperances: 2, + transactions: [ + 'bb0ec3b96209fac9529570ea6f83a86af2cceedde4aaf2bfcc4796680d23f1c7', + '01f700df84c466f2a389440e5eeacdc47d04f380c39e5d19dce2ce91a11ecba3' + ] + }, + { + addrStr: 'moZY18rGNmh4YCPeugtGW46AkkWMQttBUD', + balance: 0, + balanceSat: 0, + totalReceived: 27.82729129, + totalReceivedSat: 2782729129, + totalSent: 27.82729129, + totalSentSat: 2782729129, + unconfirmedBalance: 0, + unconfirmedBalanceSat: 0, + unconfirmedTxApperances: 0, + txApperances: 2, + transactions: [ + 'bb0ec3b96209fac9529570ea6f83a86af2cceedde4aaf2bfcc4796680d23f1c7', + '01f700df84c466f2a389440e5eeacdc47d04f380c39e5d19dce2ce91a11ecba3' + ] + } + ]); + done(); + } + }); + }); + }); + describe('/addr/:addr/utxo', function() { it('should have correct data', function(done) { var insight = [ @@ -455,6 +552,7 @@ describe('Addresses', function() { } ], "blockhash": "0000000000000041ddc94ecf4f86a456a83b2e320c36c6f0c13ff92c7e75f013", + "blockheight": 534181, "confirmations": 52, "time": 1441116143, "blocktime": 1441116143, diff --git a/test/blocks.js b/test/blocks.js index eea891c2d..84cd37b78 100644 --- a/test/blocks.js +++ b/test/blocks.js @@ -1,10 +1,10 @@ 'use strict'; -var should = require('should'); -var sinon = require('sinon'); +var _ = require('lodash'); var BlockController = require('../lib/blocks'); var bitcore = require('bitcore-lib'); -var _ = require('lodash'); +var sinon = require('sinon'); +var should = require('should'); var blocks = require('./data/blocks.json'); @@ -69,6 +69,7 @@ describe('Blocks', function() { var bitcoreBlock = bitcore.Block.fromBuffer(new Buffer(blocks['0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7'], 'hex')); var node = { + name: 'node1', getBlock: sinon.stub().callsArgWith(1, null, bitcoreBlock), services: { bitcoind: { @@ -103,6 +104,7 @@ describe('Blocks', function() { it('block pool info should be correct', function(done) { var block = bitcore.Block.fromString(blocks['000000000000000004a118407a4e3556ae2d5e882017e7ce526659d8073f13a4']); var node = { + name: 'node2', getBlock: sinon.stub().callsArgWith(1, null, block), services: { bitcoind: { @@ -122,7 +124,10 @@ describe('Blocks', function() { var res = {}; var next = function() { should.exist(req.block); - var block = req.block; + should.exist(req.block.poolInfo); + should.exist(req.block.poolInfo.poolName); + should.exist(req.block.poolInfo.url); + req.block.poolInfo.poolName.should.equal('Discus Fish'); req.block.poolInfo.url.should.equal('http://f2pool.com/'); done(); @@ -133,6 +138,72 @@ describe('Blocks', function() { controller.block(req, res, next, hash); }); + it('should return combined result on batch request', function(done) { + 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 req = {}; + + var node = { + getBlock: stub, + services: { + bitcoind: { + getNextBlockHash: sinon.stub().returns('000000000001e866a8057cde0c650796cb8a59e0e6038dc31c69d7ca6649627d'), + getBlockIndex: sinon.stub().returns(blockIndexes['000000000000000004a118407a4e3556ae2d5e882017e7ce526659d8073f13a4']), + isMainChain: sinon.stub().returns(true) + }, + db: { + tip: { + __height: 534092 + } + } + } + }; + + var controller = new BlockController(node); + + controller.block(req, {}, function(data) { + console.log('data'); + console.log(data); + should.exist(req.block); + req.block.should.be.instanceof(Array).and.have.lengthOf(2); + should.exist(req.block[0].hash); + req.block[0].hash.should.be.equal('000000000008fbb2e358e382a6f6948b2da24563bba183af447e6e2542e8efc7'); + should.exist(req.block[1].hash); + req.block[1].hash.should.be.equal('00000000000006bd8fe9e53780323c0e85719eca771022e1eb6d10c62195c441'); + done(); + }, [ + '000000000008fbb2e358e382a6f6948b2da24563bba183af447e6e2542e8efc7', + '00000000000006bd8fe9e53780323c0e85719eca771022e1eb6d10c62195c441' + ].join(',')); + }); + + it('should graceful fail on error', function(done) { + var error = { + code: 123, + message: 'some error' + }; + + var controller = new BlockController({ + getBlock: sinon.stub().callsArgWith(1, error) + }); + + var res = { + status: sinon.stub().returns({ + send: sinon.spy() + }) + }; + + controller.block({}, res, function() { + assert(false, 'should not call next middleware on fail'); + }, ''); + + setTimeout(function() { + res.status.callCount.should.equal(1); + res.status.args[0][0].should.equal(400); + done(); + }); + }); }); describe('/blocks route', function() { diff --git a/test/common.js b/test/common.js new file mode 100644 index 000000000..1d3dd0844 --- /dev/null +++ b/test/common.js @@ -0,0 +1,56 @@ +var common = require('../lib/common'); +var should = require('should'); +var sinon = require('sinon'); + +describe('common', function() { + function invokeHandle(index, value, handler, err, result) { + var callback; + + handler.args[index][0].should.equal(value); + callback = handler.args[index][1]; + callback(err, result); + } + + describe('batch', function() { + var handler; + var next; + var outkey; + var req; + var res; + + beforeEach(function() { + handler = sinon.stub(); + next = sinon.spy(); + outkey = 'qwerty'; + req = {}; + res = {}; + }); + + it('should single input data', function() { + common.batch(handler, outkey)(req, res, next, '123'); + + handler.callCount.should.equal(1); + invokeHandle(0, '123', handler, null, 'r123'); + handler.callCount.should.equal(1); + + should(req).have.property(outkey, 'r123'); + }); + + it('should multi input data', function() { + common.batch(handler, outkey)(req, res, next, '0,1,2'); + + handler.callCount.should.equal(1); + invokeHandle(0, '0', handler, null, 'r0'); + handler.callCount.should.equal(2); + invokeHandle(1, '1', handler, null, 'r1'); + handler.callCount.should.equal(3); + invokeHandle(2, '2', handler, null, 'r2'); + handler.callCount.should.equal(3); + + should(req).have.property(outkey).with.lengthOf(3); + req[outkey][0].should.equal('r0'); + req[outkey][1].should.equal('r1'); + req[outkey][2].should.equal('r2'); + }); + }); +}); diff --git a/test/transactions.js b/test/transactions.js index 06f969207..c1ea31899 100644 --- a/test/transactions.js +++ b/test/transactions.js @@ -80,6 +80,7 @@ describe('Transactions', function() { } ], "blockhash": "0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7", + "blockheight": 533974, "confirmations": 230, "time": 1440987503, "blocktime": 1440987503, @@ -213,6 +214,7 @@ describe('Transactions', function() { var blockHex = '07000020a491892cca9f143f7f00b8d65bbce0204bb32e17e914325fa5010000000000003e28f0519ecf01f7f01ea8da61084b2e4741a18ce1f3738117b84458353764b06fb9e35567f20c1a78eb626f0301000000010000000000000000000000000000000000000000000000000000000000000000ffffffff2303d6250800feb0aae355fe263600000963676d696e6572343208ae5800000000000000ffffffff01c018824a000000001976a91468bedce8982d25c3b6b03f6238cbad00378b8ead88ac000000000100000002ad5a14ae9d0f3221b790c4fc590fddceea1456e5692d8c4bf1ff7175f2b0c987000000008b4830450221008e5df62719cd92d7b137d00bbd27f153f2909bcad3a300960bc1020ec6d5e961022039df51600ff4fb5da5a794d1648c6b47c1f7d277fd5877fb5e52a730a3595f8c014104eb1e0ccd9afcac42229348dd776e991c69551ae3474340fada12e787e51758397e1d3afdba360d6374261125ea3b6ea079a5f202c150dfd729e1062d9176a307ffffffff9621ac65bc22ea593ca9a61a8d63e461bf3d3f277989df5d3bd33ddfae0aa1d8000000008a4730440220761464d7bab9515d92260762a97af82a9b25d202d8f7197b1aaec81b6fed541f022059f99606de6b06e17b2cd102dceb3807ebdd9e777a5b77c9a0b3672f5eabcb31014104eb1e0ccd9afcac42229348dd776e991c69551ae3474340fada12e787e51758397e1d3afdba360d6374261125ea3b6ea079a5f202c150dfd729e1062d9176a307ffffffff02dc374401000000001976a9144b7b335f978f130269fe661423258ae9642df8a188ac72b3d000000000001976a9146efcf883b4b6f9997be9a0600f6c095fe2bd2d9288ac000000000100000002060d3cb6dfb7ffe85e2908010fea63190c9707e96fc7448128eb895b5e222771030000006b483045022100f67cffc0ae23adb236ff3edb4a9736e277605db30cc7708dfab8cf1e1483bbce022052396aa5d664ec1cb65992c423fd9a17e94dc7af328d2d559e90746dd195ca5901210346134da14907581d8190d3980caaf46d95e4eb9c1ca8e70f1fc6007fefb1909dfeffffff7b2d8a8263cffbdb722e2a5c74166e6f2258634e277c0b08f51b578b667e2fba000000006a473044022077222a91cda23af69179377c62d84a176fb12caff6c5cbf6ae9e5957ff3b1afe0220768edead76819228dcba18cca3c9a5a5d4c32919720f21df21a297ba375bbe5c012103371ea5a4dfe356b3ea4042a537d7ab7ee0faabd43e21b6cc076fda2240629eeefeffffff02209a1d00000000001976a9148e451eec7ca0a1764b4ab119274efdd2727b3c8588ac40420f00000000001976a914d0fce8f064cd1059a6a11501dd66fe42368572b088accb250800'; var blockIndex = { "hash": "0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7", + "blockheight": 533974, "chainWork": "0000000000000000000000000000000000000000000000054626b1839ade284a", "prevHash": "00000000000001a55f3214e9172eb34b20e0bc5bd6b8007f3f149fca2c8991a4", "height": 533974 @@ -295,6 +297,7 @@ describe('Transactions', function() { } ], "blockhash": "0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7", + "blockheight": 533974, "confirmations": 236, "time": 1440987503, "blocktime": 1440987503, @@ -368,6 +371,7 @@ describe('Transactions', function() { } ], "blockhash": "0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7", + "blockheight": 533974, "confirmations": 236, "time": 1440987503, "blocktime": 1440987503, @@ -445,6 +449,7 @@ describe('Transactions', function() { } ], "blockhash": "0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7", + "blockheight": 533974, "confirmations": 236, "time": 1440987503, "blocktime": 1440987503, @@ -527,6 +532,241 @@ describe('Transactions', function() { transactions.list(req, res); }); + + it('should give the correct data', function(done) { + var insight = [ + { + "blockhash": "0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7", + "blockheight": 533974, + "blocktime": 1440987503, + "confirmations": 230, + "fees": 0.0003, + "locktime": 0, + "size": 437, + "time": 1440987503, + "txid": "b85334bf2df35c6dd5b294efe92ffc793a78edff75a2ca666fc296ffb04bbba0", + "valueIn": 0.3495539, + "valueOut": 0.3492539, + "version": 1, + "vin": [ + { + "addr": "mqdofsXHpePPGBFXuwwypAqCcXi48Xhb2f", + "doubleSpentTxID": null, + "n": 0, + "scriptSig": { + "asm": "30450221008e5df62719cd92d7b137d00bbd27f153f2909bcad3a300960bc1020ec6d5e961022039df51600ff4fb5da5a794d1648c6b47c1f7d277fd5877fb5e52a730a3595f8c01 04eb1e0ccd9afcac42229348dd776e991c69551ae3474340fada12e787e51758397e1d3afdba360d6374261125ea3b6ea079a5f202c150dfd729e1062d9176a307", + "hex": "4830450221008e5df62719cd92d7b137d00bbd27f153f2909bcad3a300960bc1020ec6d5e961022039df51600ff4fb5da5a794d1648c6b47c1f7d277fd5877fb5e52a730a3595f8c014104eb1e0ccd9afcac42229348dd776e991c69551ae3474340fada12e787e51758397e1d3afdba360d6374261125ea3b6ea079a5f202c150dfd729e1062d9176a307" + }, + "sequence": 4294967295, + "txid": "87c9b0f27571fff14b8c2d69e55614eacedd0f59fcc490b721320f9dae145aad", + "value": 0.18535505, + "valueSat": 18535505, + "vout": 0 + }, + { + "addr": "mqdofsXHpePPGBFXuwwypAqCcXi48Xhb2f", + "doubleSpentTxID": null, + "n": 1, + "scriptSig": { + "asm": "30440220761464d7bab9515d92260762a97af82a9b25d202d8f7197b1aaec81b6fed541f022059f99606de6b06e17b2cd102dceb3807ebdd9e777a5b77c9a0b3672f5eabcb3101 04eb1e0ccd9afcac42229348dd776e991c69551ae3474340fada12e787e51758397e1d3afdba360d6374261125ea3b6ea079a5f202c150dfd729e1062d9176a307", + "hex": "4730440220761464d7bab9515d92260762a97af82a9b25d202d8f7197b1aaec81b6fed541f022059f99606de6b06e17b2cd102dceb3807ebdd9e777a5b77c9a0b3672f5eabcb31014104eb1e0ccd9afcac42229348dd776e991c69551ae3474340fada12e787e51758397e1d3afdba360d6374261125ea3b6ea079a5f202c150dfd729e1062d9176a307" + }, + "sequence": 4294967295, + "txid": "d8a10aaedf3dd33b5ddf8979273f3dbf61e4638d1aa6a93c59ea22bc65ac2196", + "value": 0.16419885, + "valueSat": 16419885, + "vout": 0 + } + ], + "vout": [ + { + "n": 0, + "scriptPubKey": { + "addresses": [ + "mnQ4ZaGessNgdxmWPxbTHcfx4b8R6eUr1X" + ], + "asm": "OP_DUP OP_HASH160 4b7b335f978f130269fe661423258ae9642df8a1 OP_EQUALVERIFY OP_CHECKSIG", + "hex": "76a9144b7b335f978f130269fe661423258ae9642df8a188ac", + "type": "pubkeyhash" + }, + "value": "0.21247964" + }, + { + "n": 1, + "scriptPubKey": { + "addresses": [ + "mqdofsXHpePPGBFXuwwypAqCcXi48Xhb2f" + ], + "asm": "OP_DUP OP_HASH160 6efcf883b4b6f9997be9a0600f6c095fe2bd2d92 OP_EQUALVERIFY OP_CHECKSIG", + "hex": "76a9146efcf883b4b6f9997be9a0600f6c095fe2bd2d9288ac", + "type": "pubkeyhash" + }, + "spentIndex": 1, + "spentTxId": "614fe1708825f9c21732394e4784cc6808ac1d8b939736bfdead970567561eec", + "value": "0.13677426" + } + ] + }, + { + "blockhash": "0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7", + "blockheight": 533974, + "blocktime": 1440987503, + "confirmations": 230, + "fees": 0.0003, + "locktime": 0, + "size": 437, + "time": 1440987503, + "txid": "b85334bf2df35c6dd5b294efe92ffc793a78edff75a2ca666fc296ffb04bbba0", + "valueIn": 0.3495539, + "valueOut": 0.3492539, + "version": 1, + "vin": [ + { + "addr": "mqdofsXHpePPGBFXuwwypAqCcXi48Xhb2f", + "doubleSpentTxID": null, + "n": 0, + "scriptSig": { + "asm": "30450221008e5df62719cd92d7b137d00bbd27f153f2909bcad3a300960bc1020ec6d5e961022039df51600ff4fb5da5a794d1648c6b47c1f7d277fd5877fb5e52a730a3595f8c01 04eb1e0ccd9afcac42229348dd776e991c69551ae3474340fada12e787e51758397e1d3afdba360d6374261125ea3b6ea079a5f202c150dfd729e1062d9176a307", + "hex": "4830450221008e5df62719cd92d7b137d00bbd27f153f2909bcad3a300960bc1020ec6d5e961022039df51600ff4fb5da5a794d1648c6b47c1f7d277fd5877fb5e52a730a3595f8c014104eb1e0ccd9afcac42229348dd776e991c69551ae3474340fada12e787e51758397e1d3afdba360d6374261125ea3b6ea079a5f202c150dfd729e1062d9176a307" + }, + "sequence": 4294967295, + "txid": "87c9b0f27571fff14b8c2d69e55614eacedd0f59fcc490b721320f9dae145aad", + "value": 0.18535505, + "valueSat": 18535505, + "vout": 0 + }, + { + "addr": "mqdofsXHpePPGBFXuwwypAqCcXi48Xhb2f", + "doubleSpentTxID": null, + "n": 1, + "scriptSig": { + "asm": "30440220761464d7bab9515d92260762a97af82a9b25d202d8f7197b1aaec81b6fed541f022059f99606de6b06e17b2cd102dceb3807ebdd9e777a5b77c9a0b3672f5eabcb3101 04eb1e0ccd9afcac42229348dd776e991c69551ae3474340fada12e787e51758397e1d3afdba360d6374261125ea3b6ea079a5f202c150dfd729e1062d9176a307", + "hex": "4730440220761464d7bab9515d92260762a97af82a9b25d202d8f7197b1aaec81b6fed541f022059f99606de6b06e17b2cd102dceb3807ebdd9e777a5b77c9a0b3672f5eabcb31014104eb1e0ccd9afcac42229348dd776e991c69551ae3474340fada12e787e51758397e1d3afdba360d6374261125ea3b6ea079a5f202c150dfd729e1062d9176a307" + }, + "sequence": 4294967295, + "txid": "d8a10aaedf3dd33b5ddf8979273f3dbf61e4638d1aa6a93c59ea22bc65ac2196", + "value": 0.16419885, + "valueSat": 16419885, + "vout": 0 + } + ], + "vout": [ + { + "n": 0, + "scriptPubKey": { + "addresses": [ + "mnQ4ZaGessNgdxmWPxbTHcfx4b8R6eUr1X" + ], + "asm": "OP_DUP OP_HASH160 4b7b335f978f130269fe661423258ae9642df8a1 OP_EQUALVERIFY OP_CHECKSIG", + "hex": "76a9144b7b335f978f130269fe661423258ae9642df8a188ac", + "type": "pubkeyhash" + }, + "value": "0.21247964" + }, + { + "n": 1, + "scriptPubKey": { + "addresses": [ + "mqdofsXHpePPGBFXuwwypAqCcXi48Xhb2f" + ], + "asm": "OP_DUP OP_HASH160 6efcf883b4b6f9997be9a0600f6c095fe2bd2d92 OP_EQUALVERIFY OP_CHECKSIG", + "hex": "76a9146efcf883b4b6f9997be9a0600f6c095fe2bd2d9288ac", + "type": "pubkeyhash" + }, + "spentIndex": 1, + "spentTxId": "614fe1708825f9c21732394e4784cc6808ac1d8b939736bfdead970567561eec", + "value": "0.13677426" + } + ] + } + ]; + + var bitcoreTxObj = { + "hash": "b85334bf2df35c6dd5b294efe92ffc793a78edff75a2ca666fc296ffb04bbba0", + "version": 1, + "inputs": [ + { + "prevTxId": "87c9b0f27571fff14b8c2d69e55614eacedd0f59fcc490b721320f9dae145aad", + "outputIndex": 0, + "sequenceNumber": 4294967295, + "script": "4830450221008e5df62719cd92d7b137d00bbd27f153f2909bcad3a300960bc1020ec6d5e961022039df51600ff4fb5da5a794d1648c6b47c1f7d277fd5877fb5e52a730a3595f8c014104eb1e0ccd9afcac42229348dd776e991c69551ae3474340fada12e787e51758397e1d3afdba360d6374261125ea3b6ea079a5f202c150dfd729e1062d9176a307", + "scriptString": "72 0x30450221008e5df62719cd92d7b137d00bbd27f153f2909bcad3a300960bc1020ec6d5e961022039df51600ff4fb5da5a794d1648c6b47c1f7d277fd5877fb5e52a730a3595f8c01 65 0x04eb1e0ccd9afcac42229348dd776e991c69551ae3474340fada12e787e51758397e1d3afdba360d6374261125ea3b6ea079a5f202c150dfd729e1062d9176a307", + "output": { + "satoshis": 18535505, + "script": "76a9146efcf883b4b6f9997be9a0600f6c095fe2bd2d9288ac" + } + }, + { + "prevTxId": "d8a10aaedf3dd33b5ddf8979273f3dbf61e4638d1aa6a93c59ea22bc65ac2196", + "outputIndex": 0, + "sequenceNumber": 4294967295, + "script": "4730440220761464d7bab9515d92260762a97af82a9b25d202d8f7197b1aaec81b6fed541f022059f99606de6b06e17b2cd102dceb3807ebdd9e777a5b77c9a0b3672f5eabcb31014104eb1e0ccd9afcac42229348dd776e991c69551ae3474340fada12e787e51758397e1d3afdba360d6374261125ea3b6ea079a5f202c150dfd729e1062d9176a307", + "scriptString": "71 0x30440220761464d7bab9515d92260762a97af82a9b25d202d8f7197b1aaec81b6fed541f022059f99606de6b06e17b2cd102dceb3807ebdd9e777a5b77c9a0b3672f5eabcb3101 65 0x04eb1e0ccd9afcac42229348dd776e991c69551ae3474340fada12e787e51758397e1d3afdba360d6374261125ea3b6ea079a5f202c150dfd729e1062d9176a307", + "output": { + "satoshis": 16419885, + "script": "76a9146efcf883b4b6f9997be9a0600f6c095fe2bd2d9288ac" + } + } + ], + "outputs": [ + { + "satoshis": 21247964, + "script": "76a9144b7b335f978f130269fe661423258ae9642df8a188ac" + }, + { + "satoshis": 13677426, + "script": "76a9146efcf883b4b6f9997be9a0600f6c095fe2bd2d9288ac" + } + ], + "nLockTime": 0 + }; + + var spentTxId = '614fe1708825f9c21732394e4784cc6808ac1d8b939736bfdead970567561eec'; + var spentIndex = 1; + + var bitcoreTx = bitcore.Transaction(bitcoreTxObj); + bitcoreTx.__blockHash = '0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7'; + bitcoreTx.__height = 533974; + bitcoreTx.__timestamp = 1440987503; + bitcoreTx.populateInputs = sinon.stub().callsArg(2); + bitcoreTx.toObject = sinon.stub().returns(bitcoreTxObj); + + var transactions = new TxController({ + getTransactionWithBlockInfo: sinon.stub().callsArgWith(2, null, bitcoreTx), + services: { + db: { + tip: { + __height: 534203 + } + }, + address: { + getInputForOutput: function(txid, outputIndex, options, callback) { + var data = false; + if (txid === 'b85334bf2df35c6dd5b294efe92ffc793a78edff75a2ca666fc296ffb04bbba0' && + outputIndex === 1) { + data = { + inputTxId: spentTxId, + inputIndex: spentIndex + } + } + setImmediate(function() { + callback(null, data); + }); + } + } + }, + network: 'testnet' + }); + + var req = {}; + + transactions.multitransactions(req, {}, function() { + should.exist(insight); + req.transaction.should.eql(insight); + done(); + }, 'b85334bf2df35c6dd5b294efe92ffc793a78edff75a2ca666fc296ffb04bbba0,87c9b0f27571fff14b8c2d69e55614eacedd0f59fcc490b721320f9dae145aad'); + }); + it('by address', function(done) { var txinfos = [ @@ -700,6 +940,7 @@ describe('Transactions', function() { } ], "blockhash": "00000000000001001aba15de213648f370607fb048288dd27b96f7e833a73520", + "blockheight": 534105, "confirmations": 119, "time": 1441068774, "blocktime": 1441068774, @@ -763,6 +1004,7 @@ describe('Transactions', function() { } ], "blockhash": "0000000000000a3acc1f7fe72917eb48bb319ed96c125a6dfcc0ba6acab3c4d0", + "blockheight": 534110, "confirmations": 114, "time": 1441072817, "blocktime": 1441072817, @@ -854,6 +1096,37 @@ describe('Transactions', function() { }); }); + describe('/rawtxs/:txids', function() { + it('should give the correct data', function(done) { + var hexes = [ + '01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff2303d6250800feb0aae355fe263600000963676d696e6572343208ae5800000000000000ffffffff01c018824a000000001976a91468bedce8982d25c3b6b03f6238cbad00378b8ead88ac00000000', + '01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff2303d6250800feb0aae355fe263600000963676d696e6572343208ae5800000000000000ffffffff01c018824a000000001976a91468bedce8982d25c3b6b03f6238cbad00378b8ead88ac00000000' + ]; + + var stub = sinon.stub(); + + stub + .onFirstCall() + .callsArgWith(2, null, bitcore.Transaction().fromBuffer(new Buffer(hexes[0], 'hex'))); + stub + .onSecondCall() + .callsArgWith(2, null, bitcore.Transaction().fromBuffer(new Buffer(hexes[1], 'hex'))); + + var req = {}; + var transactions = new TxController({ + getTransaction: stub + }); + + transactions.multiRawTransaction(req, {}, function() { + should(req.rawTransaction.rawtx).eql(hexes); + done(); + }, [ + '25a988e54b02e0e5df146a0f8fa7b9db56210533a9f04bdfda5f4ceb6f77aadd', + 'b85334bf2df35c6dd5b294efe92ffc793a78edff75a2ca666fc296ffb04bbba0' + ].join(',')); + }); + }); + describe('#transformInvTransaction', function() { it('should give the correct data', function() { var insight = {