Merge pull request #63 from maraoz/polish/blocks

Polish /v1/blocks calls
This commit is contained in:
Manuel Aráoz 2015-04-29 21:49:47 -03:00
commit cc5383c38c
14 changed files with 112 additions and 64 deletions

View File

@ -30,6 +30,9 @@ Blocks.blockHashParam = function(req, res, next, blockHash) {
.then(next)
.catch(BitcoreNode.errors.Blocks.NotFound, function() {
res.status(404).send('Block with id ' + blockHash + ' not found');
})
.catch(function() {
console.log(arguments);
});
};
@ -45,6 +48,9 @@ Blocks.heightParam = function(req, res, next, height) {
.then(next)
.catch(BitcoreNode.errors.Blocks.NotFound, function() {
res.status(404).send('Block with height ' + height + ' not found');
})
.catch(function() {
console.log(arguments);
});
};
@ -68,17 +74,38 @@ Blocks.list = function(req, res) {
var offset = parseInt(req.query.offset || 0);
var limit = parseInt(req.query.limit || 10);
if (from < 0) {
res.status(422);
res.send('/v1/blocks/ "from" must be valid block height (a positive integer)');
return;
}
if (to < 0) {
res.status(422);
res.send('/v1/blocks/ "to" must be valid block height (a positive integer)');
return;
}
if (offset < 0) {
res.status(422);
res.send('/v1/blocks/ "offset" must be a positive integer');
return;
}
if (limit < 0) {
res.status(422);
res.send('/v1/blocks/ "limit" must be a positive integer');
return;
}
if (to < from) {
res.status(422);
res.send('/v1/blocks/ "to" must be >= "from"');
return;
}
// TODO: add more parameter validation
// TODO: return block_summary instead of block_full
node.blockService.listBlocks(from, to, offset, limit)
.then(function(blocks) {
res.send(blocks);
res.send(blocks.map(function(b) {
return b.toObject();
}));
});
};
@ -91,7 +118,7 @@ Blocks.getLatest = function(req, res) {
};
Blocks.get = function(req, res) {
$.checkState(req.block instanceof Block);
$.checkState(req.block instanceof Block, JSON.stringify(req.block));
res.send(req.block.toObject());
};

View File

@ -32,6 +32,9 @@ Transactions.txHashParam = function(req, res, next, txHash) {
.then(next)
.catch(BitcoreNode.errors.Transactions.NotFound, function() {
res.status(404).send('Transaction with id ' + txHash + ' not found');
})
.catch(function() {
console.log(arguments);
});
};

View File

@ -1,7 +1,20 @@
'use strict';
var BitcoreHTTP = require('../lib/http');
var bitcore = require('bitcore');
var _app = null;
module.exports = function(nodeMock) {
return process.env.INTEGRATION === 'true' ? BitcoreHTTP.create().app : new BitcoreHTTP(nodeMock).app;
if (process.env.INTEGRATION === 'true') {
if (_app) {
return _app;
}
var config = require('config');
var network = config.get('BitcoreHTTP.BitcoreNode').network;
console.log('Starting test suite', network, 'network');
bitcore.Networks.defaultNetwork = bitcore.Networks.get(network);
_app = BitcoreHTTP.create(config.get('BitcoreHTTP')).app;
return _app;
}
return new BitcoreHTTP(nodeMock).app;
};

2
api/test/mocha.opts Normal file
View File

@ -0,0 +1,2 @@
--recursive
-R spec

View File

@ -53,17 +53,22 @@ describe('BitcoreHTTP v1 blocks routes', function() {
var start = from - 1e5;
var end = to - 1e5;
var section = blockList.slice(start, end);
return Promise.resolve(section.slice(offset, offset + limit));
var ret = section.slice(offset, offset + limit);
return Promise.resolve(ret);
};
app = require('../app')(nodeMock);
agent = request(app);
});
var toObject = function(b) {
return b.toObject();
};
describe('/blocks', function() {
it('works with default parameters', function(cb) {
agent.get('/v1/blocks/')
agent.get('/v1/blocks/?from=100000')
.expect(200)
.expect(JSON.stringify(blockList), cb);
.expect(blockList.map(toObject), cb);
});
it('fails with to<from', function(cb) {
agent.get('/v1/blocks/?from=100000&to=99999')
@ -73,29 +78,33 @@ describe('BitcoreHTTP v1 blocks routes', function() {
it('works with to/from parameters', function(cb) {
agent.get('/v1/blocks/?from=100000&to=100001')
.expect(200)
.expect(JSON.stringify([firstBlock]), cb);
.expect([firstBlock.toObject()], cb);
});
it('works with limit/offset parameters', function(cb) {
agent.get('/v1/blocks/?limit=1&offset=1')
agent.get('/v1/blocks/?from=100000&limit=1&offset=1')
.expect(200)
.expect(JSON.stringify([secondBlock]), cb);
.expect([secondBlock.toObject()], cb);
});
it('works with all parameters', function(cb) {
agent.get('/v1/blocks/?from=100005&to=100020&limit=3&offset=2')
.expect(200)
.expect(JSON.stringify(last3), cb);
.expect(last3.map(toObject), cb);
});
it('works with all parameters 2', function(cb) {
agent.get('/v1/blocks/?from=100000&to=100005&limit=2&offset=2')
.expect(200)
.expect(JSON.stringify(some2), cb);
.expect(some2.map(toObject), cb);
});
});
describe('/blocks/latest', function() {
it('returns latest block', function(cb) {
if (process.env.INTEGRATION === 'true') {
// can't test this as latest block will always change
return cb();
}
agent.get('/v1/blocks/latest')
.expect(200)
.expect(lastBlock.toJSON(), cb);
.expect(lastBlock.toObject(), cb);
});
});
describe('/blocks/:blockHash', function() {

View File

@ -21,6 +21,7 @@ describe('BitcoreHTTP v1 node routes', function() {
peerCount: 8,
version: 'test',
network: 'test',
height: 1234,
};
nodeMock.getStatus = function() {
return Promise.resolve(nodeMock.status);

View File

@ -15,7 +15,7 @@ var BitcoreHTTP = require('../../lib/http');
var BitcoreNode = require('../../../');
var mockTransactions = require('../data/transactions');
describe('BitcoreHTTP v1 transactions routes', function() {
describe.only('BitcoreHTTP v1 transactions routes', function() {
// mocks
var mockValidTx = new Transaction();
@ -37,7 +37,7 @@ describe('BitcoreHTTP v1 transactions routes', function() {
}
return Promise.resolve();
};
app = new BitcoreHTTP(nodeMock).app;
app = require('../app')(nodeMock);
agent = request(app);
});

View File

@ -188,6 +188,7 @@ BitcoreNode.prototype.getStatus = function() {
peerCount: this.networkMonitor.getConnectedPeers(),
version: pjson.version,
network: bitcore.Networks.defaultNetwork.name,
height: this.blockchain.getCurrentHeight(),
});
};

View File

@ -91,7 +91,7 @@ BlockService.blockRPCtoBitcore = function(blockData) {
nonce: blockData.nonce,
bits: new bitcore.deps.bnjs(
new bitcore.deps.Buffer(blockData.bits, 'hex')
),
).toNumber(),
merkleRoot: bitcore.util.buffer.reverse(
new bitcore.deps.Buffer(blockData.merkleroot, 'hex')
)
@ -109,11 +109,11 @@ BlockService.blockRPCtoBitcore = function(blockData) {
* @return {Promise} a promise that will always be rejected
*/
var blockNotFound = function(err) {
if (err) {
throw err;
if (err.message === 'Block not found' ||
err.message === 'Block height out of range') {
throw new errors.Blocks.NotFound();
}
var hash = err;
throw new errors.Blocks.NotFound(hash);
throw err;
};
/**
@ -192,6 +192,7 @@ BlockService.prototype.getBlockByHeight = function(height) {
* @param {Number} to ditto, but for the upper limit, non inclusive
* @param {Number} offset skip the first offset blocks
* @param {Number} limit max amount of blocks returned
* @return {Array} a list of blocks
*
*/
BlockService.prototype.listBlocks = function(from, to, offset, limit) {
@ -203,21 +204,22 @@ BlockService.prototype.listBlocks = function(from, to, offset, limit) {
var self = this;
var start = from + offset;
var end = Math.min(to, start + limit - 1);
var end = Math.min(to, start + limit);
var blocks = [];
//console.log(from, to, offset, limit);
//console.log(start, end);
// TODO: optimize: precompute heights and fetch all blocks in parallel?
var fetchBlock = function(height) {
if (height > end) {
return;
if (height >= end) {
return Promise.resolve();
}
console.log('fetching block', height);
return self.getBlockByHeight(height)
.then(function(block) {
blocks.push(block.toObject());
blocks.push(block);
return fetchBlock(height + 1);
})
.catch(function(err) {
console.log(err);
// block not found, ignore
});
};
return fetchBlock(start)

View File

@ -20,6 +20,7 @@ var LevelUp = require('levelup');
var Promise = require('bluebird');
var bitcore = require('bitcore');
var config = require('config');
var BitcoreNode = require('../../');
var _ = bitcore.deps._;
var $ = bitcore.util.preconditions;
@ -53,7 +54,7 @@ var Index = {
output: 'txo-', // txo-<txid>-<n> -> serialized Output
spent: 'txs-', // txo-<txid>-<n>-<spend txid>-<m> -> block height of confirmation for spend
address: 'txa-', // txa-<address>-<txid>-<n> -> Output
addressSpent: 'txas-',
addressSpent: 'txas-',
// txa-<address>-<txid>-<n> -> {
// heightSpent: number, (may be -1 for unconfirmed tx)
// spentTx: string, spentTxInputIndex: number, spendInput: Input
@ -84,10 +85,14 @@ function TransactionService(opts) {
}
TransactionService.Index = Index;
TransactionService.transactionRPCtoBitcore = function(rpcResponse) {
if (rpcResponse.error) {
throw new bitcore.Error(rpcResponse.error);
var txNotFound = function(error) {
if (error.message === 'No information available about transaction') {
throw new BitcoreNode.errors.Transactions.NotFound();
}
throw error;
};
TransactionService.transactionRPCtoBitcore = function(rpcResponse) {
return new bitcore.Transaction(rpcResponse.result);
};
@ -99,10 +104,12 @@ TransactionService.prototype.getTransaction = function(transactionId) {
}
return Promise.try(function() {
return self.rpc.getRawTransactionAsync(transactionId);
}).then(function(rawTransaction) {
return TransactionService.transactionRPCtoBitcore(rawTransaction);
});
return self.rpc.getRawTransactionAsync(transactionId);
})
.catch(txNotFound)
.then(function(rawTransaction) {
return TransactionService.transactionRPCtoBitcore(rawTransaction);
});
};
TransactionService.prototype._confirmOutput = function(ops, block, transaction) {

View File

@ -34,8 +34,8 @@
"dependencies": {
"async": "0.9.0",
"bitcoind-rpc": "^0.2.1",
"bitcore": "bitpay/bitcore",
"bitcore-p2p": "^0.13.0",
"bitcore": "^0.12.0",
"bitcore-p2p": "^0.15.0",
"bluebird": "^2.9.12",
"body-parser": "^1.12.0",
"bufferput": "bitpay/node-bufferput",

View File

@ -67,7 +67,6 @@ describe('NetworkMonitor', function() {
it('broadcasts errors in underlying peer', function(cb) {
var nm = new NetworkMonitor(busMock, peerMock);
nm.on('error', function() {
console.log('under');
cb();
});
nm.start();

View File

@ -80,7 +80,7 @@ describe('BlockService', function() {
describe('block confirmation', function() {
var mockRpc, transactionMock, database, blockService, writeLock;
var mockRpc, transactionMock, database, blockService;
var thenCaller = {
then: function(arg) {
@ -118,7 +118,7 @@ describe('BlockService', function() {
var expectedOps = [{
type: 'put',
key: 'header-000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f',
value: '{"version":1,"prevHash":"0000000000000000000000000000000000000000000000000000000000000000","merkleRoot":"3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a","time":1231006505,"bits":486604799,"nonce":2083236893}'
value: '{"version":1,"prevHash":"0000000000000000000000000000000000000000000000000000000000000000","merkleRoot":"4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b","time":1231006505,"bits":486604799,"nonce":2083236893}'
}, {
type: 'put',
key: 'nxt-0000000000000000000000000000000000000000000000000000000000000000',
@ -149,10 +149,10 @@ describe('BlockService', function() {
it('makes the expected calls when confirming the block #170', function(callback) {
database.batchAsync = function(ops) {
ops.should.deep.equal([{
var eops = [{
type: 'put',
key: 'header-00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee',
value: '{"version":1,"prevHash":"55bd840a78798ad0da853f68974f3d183e2bd1db6a842c1feecf222a00000000","merkleRoot":"ff104ccb05421ab93e63f8c3ce5c2c2e9dbb37de2764b3a3175c8166562cac7d","time":1231731025,"bits":486604799,"nonce":1889418792}'
value: '{"version":1,"prevHash":"000000002a22cfee1f2c846adbd12b3e183d4f97683f85dad08a79780a84bd55","merkleRoot":"7dac2c5666815c17a3b36427de37bb9d2e2c5ccec3f8633eb91a4205cb4c10ff","time":1231731025,"bits":486604799,"nonce":1889418792}'
}, {
type: 'put',
key: 'nxt-000000002a22cfee1f2c846adbd12b3e183d4f97683f85dad08a79780a84bd55',
@ -173,7 +173,8 @@ describe('BlockService', function() {
type: 'put',
key: 'tip',
value: block170.id
}]);
}];
ops.should.deep.equal(eops);
return callback();
};
blockService.writeLock.onFirstCall().returns(thenCaller);

View File

@ -107,40 +107,23 @@ describe('TransactionService', function() {
}
});
service._confirmTransaction(ops, block170, block170.transactions[1]).then(function() {
ops.map(function(k) {
if (bitcore.util.js.isValidJSON(k.value)) {
k.value = JSON.parse(k.value);
}
return k;
}).should.deep.equal([{
ops.should.deep.equal([{
type: 'put',
key: 'btx-f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16',
value: '00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee'
}, {
type: 'put',
key: 'txo-f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16-0',
value: {
satoshis: 1000000000,
script: '65 0x04ae1a62fe09c5f51b13905f07f06b99a2f7159b2225f374cd378d71302fa28414e7aab37397f554a7df5f142c21c1b7303b8a0626f1baded5c72a704f7e6cd84c OP_CHECKSIG'
}
value: '{"satoshis":1000000000,"script":"65 0x04ae1a62fe09c5f51b13905f07f06b99a2f7159b2225f374cd378d71302fa28414e7aab37397f554a7df5f142c21c1b7303b8a0626f1baded5c72a704f7e6cd84c OP_CHECKSIG"}'
}, {
type: 'put',
key: 'txo-f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16-1',
value: {
satoshis: 4000000000,
script: '65 0x0411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3 OP_CHECKSIG'
}
value: '{"satoshis":4000000000,"script":"65 0x0411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3 OP_CHECKSIG"}'
}, {
type: 'put',
key: 'txo-f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16-0',
value: {
prevTxId: '0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9',
outputIndex: 0,
sequenceNumber: 4294967295,
script: '71 0x304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d0901',
heightConfirmed: 170
}
}, ]);
value: '{"prevTxId":"0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9","outputIndex":0,"sequenceNumber":4294967295,"script":"47304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d0901","scriptString":"71 0x304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d0901","heightConfirmed":170}'
}]);
/* TODO: This should work if address spent is accepted for public key. Add test for P2PKH if not accepted
* { type: 'put',
key: 'txas-12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S-f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16-0',