Merge pull request #63 from maraoz/polish/blocks
Polish /v1/blocks calls
This commit is contained in:
commit
cc5383c38c
@ -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());
|
||||
};
|
||||
|
||||
|
||||
@ -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);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@ -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
2
api/test/mocha.opts
Normal file
@ -0,0 +1,2 @@
|
||||
--recursive
|
||||
-R spec
|
||||
@ -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() {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
});
|
||||
|
||||
|
||||
@ -188,6 +188,7 @@ BitcoreNode.prototype.getStatus = function() {
|
||||
peerCount: this.networkMonitor.getConnectedPeers(),
|
||||
version: pjson.version,
|
||||
network: bitcore.Networks.defaultNetwork.name,
|
||||
height: this.blockchain.getCurrentHeight(),
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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',
|
||||
|
||||
Loading…
Reference in New Issue
Block a user