From eaee098cf0ee88f659031c84943e54dee876fba0 Mon Sep 17 00:00:00 2001 From: Patrick Nagurny Date: Thu, 10 Sep 2015 13:54:40 -0400 Subject: [PATCH 1/5] add getBlockHashesByTimestamp --- docs/services/db.md | 11 ++++++ lib/services/db.js | 43 +++++++++++++++++++++ test/services/db.unit.js | 80 +++++++++++++++++++++++++++++++++++++--- 3 files changed, 128 insertions(+), 6 deletions(-) diff --git a/docs/services/db.md b/docs/services/db.md index 9a8d1ebaa..aafb7e63b 100644 --- a/docs/services/db.md +++ b/docs/services/db.md @@ -32,3 +32,14 @@ node.getBlock(blockHash, function(err, block) { //... }); ``` + +Get Block Hashes by Timestamp Range + +```js +var time1 = 1441911000; // Notice time is in seconds not milliseconds +var time2 = 1441914000; + +node.getBlockHashesByTimestamp(time1, time2, function(err, hashes) { + //... +}); +``` diff --git a/lib/services/db.js b/lib/services/db.js index 62ec179c4..181bbe1c1 100644 --- a/lib/services/db.js +++ b/lib/services/db.js @@ -60,6 +60,10 @@ util.inherits(DB, Service); DB.dependencies = ['bitcoind']; +DB.PREFIXES = { + BLOCKS: 'blk' +}; + DB.prototype._setDataPath = function() { $.checkState(this.node.datadir, 'Node is expected to have a "datadir" property'); var regtest = Networks.get('regtest'); @@ -177,6 +181,7 @@ DB.prototype.transactionHandler = function(txInfo) { DB.prototype.getAPIMethods = function() { var methods = [ ['getBlock', this, this.getBlock, 1], + ['getBlockHashesByTimestamp', this, this.getBlockHashesByTimestamp, 2], ['getTransaction', this, this.getTransaction, 2], ['getTransactionWithBlockInfo', this, this.getTransactionWithBlockInfo, 2], ['sendTransaction', this, this.sendTransaction, 1], @@ -194,6 +199,37 @@ DB.prototype.getBlock = function(hash, callback) { }); }; +DB.prototype.getBlockHashesByTimestamp = function(start, end, callback) { + var hashes = []; + + var stream = this.store.createReadStream({ + start: [DB.PREFIXES.BLOCKS, start].join('-'), + end: [DB.PREFIXES.BLOCKS, end].join('-') + }); + + stream.on('data', function(data) { + hashes.push(data.value); + }); + + var error; + + stream.on('error', function(streamError) { + if (streamError) { + error = streamError; + } + }); + + stream.on('close', function() { + if (error) { + return callback(error); + } + + callback(null, hashes); + }); + + return stream; +}; + DB.prototype.getTransaction = function(txid, queryMempool, callback) { this.node.services.bitcoind.getTransaction(txid, queryMempool, function(err, txBuffer) { if (err) { @@ -371,6 +407,13 @@ DB.prototype.runAllBlockHandlers = function(block, add, callback) { this.subscriptions.block[i].emit('block', block.hash); } + // Update block index + operations.push({ + type: add ? 'put' : 'del', + key: [DB.PREFIXES.BLOCKS, block.header.timestamp].join('-'), + value: block.hash + }); + async.eachSeries( this.node.services, function(mod, next) { diff --git a/test/services/db.unit.js b/test/services/db.unit.js index 5102b9d2e..9bfc25b1f 100644 --- a/test/services/db.unit.js +++ b/test/services/db.unit.js @@ -369,6 +369,62 @@ describe('DB Service', function() { }); }); + describe('#getBlockHashesByTimestamp', function() { + it('should get the correct block hashes', function(done) { + var db = new DB(baseConfig); + var readStream = new EventEmitter(); + db.store = { + createReadStream: sinon.stub().returns(readStream) + }; + + var block1 = { + hash: '00000000050a6d07f583beba2d803296eb1e9d4980c4a20f206c584e89a4f02b', + timestamp: 1441911909 + }; + + var block2 = { + hash: '000000000383752a55a0b2891ce018fd0fdc0b6352502772b034ec282b4a1bf6', + timestamp: 1441913112 + }; + + db.getBlockHashesByTimestamp(1441911000, 1441914000, function(err, hashes) { + should.not.exist(err); + hashes.should.deep.equal([block1.hash, block2.hash]); + done(); + }); + + readStream.emit('data', { + key: 'blk-' + block1.timestamp, + value: block1.hash + }); + + readStream.emit('data', { + key: 'blk-' + block2.timestamp, + value: block2.hash + }); + + readStream.emit('close'); + }); + + it('should give an error if the stream has an error', function(done) { + var db = new DB(baseConfig); + var readStream = new EventEmitter(); + db.store = { + createReadStream: sinon.stub().returns(readStream) + }; + + db.getBlockHashesByTimestamp(1441911000, 1441914000, function(err, hashes) { + should.exist(err); + err.message.should.equal('error'); + done(); + }); + + readStream.emit('error', new Error('error')); + + readStream.emit('close'); + }); + }); + describe('#getPrevHash', function() { it('should return prevHash from bitcoind', function(done) { var db = new DB(baseConfig); @@ -650,10 +706,22 @@ describe('DB Service', function() { batch: sinon.stub().callsArg(1) }; + var block = { + hash: '00000000000000000d0aaf93e464ddeb503655a0750f8b9c6eed0bdf0ccfc863', + header: { + timestamp: 1441906365 + } + }; + it('should call blockHandler in all services and perform operations', function(done) { - db.runAllBlockHandlers('block', true, function(err) { + db.runAllBlockHandlers(block, true, function(err) { should.not.exist(err); - db.store.batch.args[0][0].should.deep.equal(['op1', 'op2', 'op3', 'op4', 'op5']); + var blockOp = { + type: 'put', + key: 'blk-1441906365', + value: '00000000000000000d0aaf93e464ddeb503655a0750f8b9c6eed0bdf0ccfc863' + } + db.store.batch.args[0][0].should.deep.equal([blockOp, 'op1', 'op2', 'op3', 'op4', 'op5']); done(); }); }); @@ -663,7 +731,7 @@ describe('DB Service', function() { Service3.prototype.blockHandler = sinon.stub().callsArgWith(2, new Error('error')); db.node.services.service3 = new Service3(); - db.runAllBlockHandlers('block', true, function(err) { + db.runAllBlockHandlers(block, true, function(err) { should.exist(err); done(); }); @@ -675,7 +743,7 @@ describe('DB Service', function() { service3: new Service3() }; - db.runAllBlockHandlers('block', true, function(err) { + db.runAllBlockHandlers(block, true, function(err) { should.not.exist(err); done(); }); @@ -688,7 +756,7 @@ describe('DB Service', function() { }; (function() { - db.runAllBlockHandlers('block', true, function(err) { + db.runAllBlockHandlers(block, true, function(err) { should.not.exist(err); }); }).should.throw('bitcore.ErrorInvalidArgument'); @@ -701,7 +769,7 @@ describe('DB Service', function() { db.node = {}; db.node.services = {}; var methods = db.getAPIMethods(); - methods.length.should.equal(5); + methods.length.should.equal(6); }); }); From 00d3a0ba67cd52762f5d9f394959c9243ee007b4 Mon Sep 17 00:00:00 2001 From: Patrick Nagurny Date: Tue, 15 Sep 2015 16:40:31 -0400 Subject: [PATCH 2/5] binary encode key and value --- lib/services/address/index.js | 4 ++-- lib/services/db.js | 29 +++++++++++++++++++++++------ test/services/address/index.unit.js | 18 +++++++++--------- test/services/db.unit.js | 14 +++++++------- 4 files changed, 41 insertions(+), 24 deletions(-) diff --git a/lib/services/address/index.js b/lib/services/address/index.js index dbf944ce5..932ac46aa 100644 --- a/lib/services/address/index.js +++ b/lib/services/address/index.js @@ -35,8 +35,8 @@ AddressService.dependencies = [ ]; AddressService.PREFIXES = { - OUTPUTS: new Buffer('32', 'hex'), - SPENTS: new Buffer('33', 'hex') + OUTPUTS: new Buffer('02', 'hex'), + SPENTS: new Buffer('03', 'hex') }; AddressService.SPACER_MIN = new Buffer('00', 'hex'); diff --git a/lib/services/db.js b/lib/services/db.js index 181bbe1c1..206a5d24d 100644 --- a/lib/services/db.js +++ b/lib/services/db.js @@ -61,7 +61,7 @@ util.inherits(DB, Service); DB.dependencies = ['bitcoind']; DB.PREFIXES = { - BLOCKS: 'blk' + BLOCKS: new Buffer('01', 'hex') }; DB.prototype._setDataPath = function() { @@ -200,15 +200,18 @@ DB.prototype.getBlock = function(hash, callback) { }; DB.prototype.getBlockHashesByTimestamp = function(start, end, callback) { + var self = this; var hashes = []; var stream = this.store.createReadStream({ - start: [DB.PREFIXES.BLOCKS, start].join('-'), - end: [DB.PREFIXES.BLOCKS, end].join('-') + start: this._encodeBlockIndexKey(start), + end: this._encodeBlockIndexKey(end), + valueEncoding: 'binary', + keyEncoding: 'binary' }); stream.on('data', function(data) { - hashes.push(data.value); + hashes.push(self._decodeBlockIndexValue(data.value)); }); var error; @@ -410,8 +413,8 @@ DB.prototype.runAllBlockHandlers = function(block, add, callback) { // Update block index operations.push({ type: add ? 'put' : 'del', - key: [DB.PREFIXES.BLOCKS, block.header.timestamp].join('-'), - value: block.hash + key: this._encodeBlockIndexKey(block.header.timestamp), + value: this._encodeBlockIndexValue(block.hash) }); async.eachSeries( @@ -445,6 +448,20 @@ DB.prototype.runAllBlockHandlers = function(block, add, callback) { ); }; +DB.prototype._encodeBlockIndexKey = function(timestamp) { + var timestampBuffer = new Buffer(4); + timestampBuffer.writeUInt32BE(timestamp); + return Buffer.concat([DB.PREFIXES.BLOCKS, timestampBuffer]); +}; + +DB.prototype._encodeBlockIndexValue = function(hash) { + return new Buffer(hash, 'hex'); +}; + +DB.prototype._decodeBlockIndexValue = function(value) { + return value.toString('hex'); +}; + /** * This function will find the common ancestor between the current chain and a forked block, * by moving backwards from the forked block until it meets the current chain. diff --git a/test/services/address/index.unit.js b/test/services/address/index.unit.js index f8f8a65c9..bc7916f0d 100644 --- a/test/services/address/index.unit.js +++ b/test/services/address/index.unit.js @@ -126,13 +126,13 @@ describe('Address Service', function() { should.not.exist(err); operations.length.should.equal(81); operations[0].type.should.equal('put'); - operations[0].key.toString('hex').should.equal('3202a61d2066d19e9e2fd348a8320b7ebd4dd3ca2b00000543abfdbefe0d064729d85556bd3ab13c3a889b685d042499c02b4aa2064fb1e1692300000000'); + operations[0].key.toString('hex').should.equal('0202a61d2066d19e9e2fd348a8320b7ebd4dd3ca2b00000543abfdbefe0d064729d85556bd3ab13c3a889b685d042499c02b4aa2064fb1e1692300000000'); operations[0].value.toString('hex').should.equal('41e2a49ec1c0000076a91402a61d2066d19e9e2fd348a8320b7ebd4dd3ca2b88ac'); operations[3].type.should.equal('put'); - operations[3].key.toString('hex').should.equal('33fdbd324b28ea69e49c998816407dc055fb81d06e00000543ab3d7d5d98df753ef2a4f82438513c509e3b11f3e738e94a7234967b03a03123a900000020'); + operations[3].key.toString('hex').should.equal('03fdbd324b28ea69e49c998816407dc055fb81d06e00000543ab3d7d5d98df753ef2a4f82438513c509e3b11f3e738e94a7234967b03a03123a900000020'); operations[3].value.toString('hex').should.equal('5780f3ee54889a0717152a01abee9a32cec1b0cdf8d5537a08c7bd9eeb6bfbca00000000'); operations[64].type.should.equal('put'); - operations[64].key.toString('hex').should.equal('329780ccd5356e2acc0ee439ee04e0fe69426c752800000543abe66f3b989c790178de2fc1a5329f94c0d8905d0d3df4e7ecf0115e7f90a6283d00000001'); + operations[64].key.toString('hex').should.equal('029780ccd5356e2acc0ee439ee04e0fe69426c752800000543abe66f3b989c790178de2fc1a5329f94c0d8905d0d3df4e7ecf0115e7f90a6283d00000001'); operations[64].value.toString('hex').should.equal('4147a6b00000000076a9149780ccd5356e2acc0ee439ee04e0fe69426c752888ac'); done(); }); @@ -149,13 +149,13 @@ describe('Address Service', function() { should.not.exist(err); operations.length.should.equal(81); operations[0].type.should.equal('del'); - operations[0].key.toString('hex').should.equal('3202a61d2066d19e9e2fd348a8320b7ebd4dd3ca2b00000543abfdbefe0d064729d85556bd3ab13c3a889b685d042499c02b4aa2064fb1e1692300000000'); + operations[0].key.toString('hex').should.equal('0202a61d2066d19e9e2fd348a8320b7ebd4dd3ca2b00000543abfdbefe0d064729d85556bd3ab13c3a889b685d042499c02b4aa2064fb1e1692300000000'); operations[0].value.toString('hex').should.equal('41e2a49ec1c0000076a91402a61d2066d19e9e2fd348a8320b7ebd4dd3ca2b88ac'); operations[3].type.should.equal('del'); - operations[3].key.toString('hex').should.equal('33fdbd324b28ea69e49c998816407dc055fb81d06e00000543ab3d7d5d98df753ef2a4f82438513c509e3b11f3e738e94a7234967b03a03123a900000020'); + operations[3].key.toString('hex').should.equal('03fdbd324b28ea69e49c998816407dc055fb81d06e00000543ab3d7d5d98df753ef2a4f82438513c509e3b11f3e738e94a7234967b03a03123a900000020'); operations[3].value.toString('hex').should.equal('5780f3ee54889a0717152a01abee9a32cec1b0cdf8d5537a08c7bd9eeb6bfbca00000000'); operations[64].type.should.equal('del'); - operations[64].key.toString('hex').should.equal('329780ccd5356e2acc0ee439ee04e0fe69426c752800000543abe66f3b989c790178de2fc1a5329f94c0d8905d0d3df4e7ecf0115e7f90a6283d00000001'); + operations[64].key.toString('hex').should.equal('029780ccd5356e2acc0ee439ee04e0fe69426c752800000543abe66f3b989c790178de2fc1a5329f94c0d8905d0d3df4e7ecf0115e7f90a6283d00000001'); operations[64].value.toString('hex').should.equal('4147a6b00000000076a9149780ccd5356e2acc0ee439ee04e0fe69426c752888ac'); done(); }); @@ -563,7 +563,7 @@ describe('Address Service', function() { }); createReadStreamCallCount.should.equal(1); var data = { - key: new Buffer('32038a213afdfc551fc658e9a2a58a86e98d69b687000000000f125dd0e50fc732d67c37b6c56be7f9dc00b6859cebf982ee2cc83ed2d604bf8700000001', 'hex'), + key: new Buffer('02038a213afdfc551fc658e9a2a58a86e98d69b687000000000f125dd0e50fc732d67c37b6c56be7f9dc00b6859cebf982ee2cc83ed2d604bf8700000001', 'hex'), value: new Buffer('41f0de058a80000076a914038a213afdfc551fc658e9a2a58a86e98d69b68788ac', 'hex') }; testStream.emit('data', data); @@ -611,12 +611,12 @@ describe('Address Service', function() { }); var data1 = { - key: new Buffer('32038a213afdfc551fc658e9a2a58a86e98d69b68700000543a8125dd0e50fc732d67c37b6c56be7f9dc00b6859cebf982ee2cc83ed2d604bf8700000001', 'hex'), + key: new Buffer('02038a213afdfc551fc658e9a2a58a86e98d69b68700000543a8125dd0e50fc732d67c37b6c56be7f9dc00b6859cebf982ee2cc83ed2d604bf8700000001', 'hex'), value: new Buffer('41f0de058a80000076a914038a213afdfc551fc658e9a2a58a86e98d69b68788ac', 'hex') }; var data2 = { - key: new Buffer('32038a213afdfc551fc658e9a2a58a86e98d69b68700000543ac3b6bc2939d1a70ce04bc4f619ee32608fbff5e565c1f9b02e4eaa97959c59ae700000002', 'hex'), + key: new Buffer('02038a213afdfc551fc658e9a2a58a86e98d69b68700000543ac3b6bc2939d1a70ce04bc4f619ee32608fbff5e565c1f9b02e4eaa97959c59ae700000002', 'hex'), value: new Buffer('40c388000000000076a914038a213afdfc551fc658e9a2a58a86e98d69b68788ac', 'hex') }; diff --git a/test/services/db.unit.js b/test/services/db.unit.js index 9bfc25b1f..aa122fb9e 100644 --- a/test/services/db.unit.js +++ b/test/services/db.unit.js @@ -394,13 +394,13 @@ describe('DB Service', function() { }); readStream.emit('data', { - key: 'blk-' + block1.timestamp, - value: block1.hash + key: db._encodeBlockIndexKey(block1.timestamp), + value: db._encodeBlockIndexValue(block1.hash) }); readStream.emit('data', { - key: 'blk-' + block2.timestamp, - value: block2.hash + key: db._encodeBlockIndexKey(block2.timestamp), + value: db._encodeBlockIndexValue(block2.hash) }); readStream.emit('close'); @@ -718,9 +718,9 @@ describe('DB Service', function() { should.not.exist(err); var blockOp = { type: 'put', - key: 'blk-1441906365', - value: '00000000000000000d0aaf93e464ddeb503655a0750f8b9c6eed0bdf0ccfc863' - } + key: db._encodeBlockIndexKey(1441906365), + value: db._encodeBlockIndexValue('00000000000000000d0aaf93e464ddeb503655a0750f8b9c6eed0bdf0ccfc863') + }; db.store.batch.args[0][0].should.deep.equal([blockOp, 'op1', 'op2', 'op3', 'op4', 'op5']); done(); }); From e6b850124c3eb020066ea001e68cc1ae33f15035 Mon Sep 17 00:00:00 2001 From: Patrick Nagurny Date: Tue, 15 Sep 2015 17:25:41 -0400 Subject: [PATCH 3/5] fixes --- lib/services/db.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/services/db.js b/lib/services/db.js index 206a5d24d..7e90b8701 100644 --- a/lib/services/db.js +++ b/lib/services/db.js @@ -199,13 +199,19 @@ DB.prototype.getBlock = function(hash, callback) { }); }; +/** + * get block hashes between two timestamps + * @param {Number} start - first timestamp, in seconds, inclusive + * @param {Number} end - second timestamp, in seconds, inclusive + * @param {Function} callback + */ DB.prototype.getBlockHashesByTimestamp = function(start, end, callback) { var self = this; var hashes = []; var stream = this.store.createReadStream({ - start: this._encodeBlockIndexKey(start), - end: this._encodeBlockIndexKey(end), + gte: this._encodeBlockIndexKey(start), + lte: this._encodeBlockIndexKey(end), valueEncoding: 'binary', keyEncoding: 'binary' }); From 7e1d433781985731c173a3e55e73ae91cfd1c3f5 Mon Sep 17 00:00:00 2001 From: Patrick Nagurny Date: Tue, 15 Sep 2015 18:23:06 -0400 Subject: [PATCH 4/5] go from highest timestamp to lowest timestamp --- docs/services/db.md | 6 +++--- lib/services/db.js | 11 ++++++----- test/services/db.unit.js | 12 ++++++------ 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/docs/services/db.md b/docs/services/db.md index aafb7e63b..5c7e31c03 100644 --- a/docs/services/db.md +++ b/docs/services/db.md @@ -36,10 +36,10 @@ node.getBlock(blockHash, function(err, block) { Get Block Hashes by Timestamp Range ```js -var time1 = 1441911000; // Notice time is in seconds not milliseconds -var time2 = 1441914000; +var newest = 1441914000; // Notice time is in seconds not milliseconds +var oldest = 1441911000; -node.getBlockHashesByTimestamp(time1, time2, function(err, hashes) { +node.getBlockHashesByTimestamp(newest, oldest, function(err, hashes) { //... }); ``` diff --git a/lib/services/db.js b/lib/services/db.js index 7e90b8701..6e4f8b93f 100644 --- a/lib/services/db.js +++ b/lib/services/db.js @@ -201,17 +201,18 @@ DB.prototype.getBlock = function(hash, callback) { /** * get block hashes between two timestamps - * @param {Number} start - first timestamp, in seconds, inclusive - * @param {Number} end - second timestamp, in seconds, inclusive + * @param {Number} high - high timestamp, in seconds, inclusive + * @param {Number} low - low timestamp, in seconds, inclusive * @param {Function} callback */ -DB.prototype.getBlockHashesByTimestamp = function(start, end, callback) { +DB.prototype.getBlockHashesByTimestamp = function(high, low, callback) { var self = this; var hashes = []; var stream = this.store.createReadStream({ - gte: this._encodeBlockIndexKey(start), - lte: this._encodeBlockIndexKey(end), + gte: this._encodeBlockIndexKey(low), + lte: this._encodeBlockIndexKey(high), + reverse: true, valueEncoding: 'binary', keyEncoding: 'binary' }); diff --git a/test/services/db.unit.js b/test/services/db.unit.js index aa122fb9e..82148b5d3 100644 --- a/test/services/db.unit.js +++ b/test/services/db.unit.js @@ -387,20 +387,20 @@ describe('DB Service', function() { timestamp: 1441913112 }; - db.getBlockHashesByTimestamp(1441911000, 1441914000, function(err, hashes) { + db.getBlockHashesByTimestamp(1441914000, 1441911000, function(err, hashes) { should.not.exist(err); - hashes.should.deep.equal([block1.hash, block2.hash]); + hashes.should.deep.equal([block2.hash, block1.hash]); done(); }); readStream.emit('data', { - key: db._encodeBlockIndexKey(block1.timestamp), - value: db._encodeBlockIndexValue(block1.hash) + key: db._encodeBlockIndexKey(block2.timestamp), + value: db._encodeBlockIndexValue(block2.hash) }); readStream.emit('data', { - key: db._encodeBlockIndexKey(block2.timestamp), - value: db._encodeBlockIndexValue(block2.hash) + key: db._encodeBlockIndexKey(block1.timestamp), + value: db._encodeBlockIndexValue(block1.hash) }); readStream.emit('close'); From a0be38f0742d4d70086282a7c068d978b02ad1f0 Mon Sep 17 00:00:00 2001 From: Patrick Nagurny Date: Wed, 16 Sep 2015 12:04:44 -0400 Subject: [PATCH 5/5] check for timestamp out of bounds --- lib/services/db.js | 12 ++++++++++-- test/services/db.unit.js | 14 ++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/lib/services/db.js b/lib/services/db.js index 6e4f8b93f..faf42c8b0 100644 --- a/lib/services/db.js +++ b/lib/services/db.js @@ -209,9 +209,16 @@ DB.prototype.getBlockHashesByTimestamp = function(high, low, callback) { var self = this; var hashes = []; + try { + var lowKey = this._encodeBlockIndexKey(low); + var highKey = this._encodeBlockIndexKey(high); + } catch(e) { + return callback(e); + } + var stream = this.store.createReadStream({ - gte: this._encodeBlockIndexKey(low), - lte: this._encodeBlockIndexKey(high), + gte: lowKey, + lte: highKey, reverse: true, valueEncoding: 'binary', keyEncoding: 'binary' @@ -456,6 +463,7 @@ DB.prototype.runAllBlockHandlers = function(block, add, callback) { }; DB.prototype._encodeBlockIndexKey = function(timestamp) { + $.checkArgument(timestamp >= 0 && timestamp <= 4294967295, 'timestamp out of bounds'); var timestampBuffer = new Buffer(4); timestampBuffer.writeUInt32BE(timestamp); return Buffer.concat([DB.PREFIXES.BLOCKS, timestampBuffer]); diff --git a/test/services/db.unit.js b/test/services/db.unit.js index 82148b5d3..c3106145f 100644 --- a/test/services/db.unit.js +++ b/test/services/db.unit.js @@ -423,6 +423,20 @@ describe('DB Service', function() { readStream.emit('close'); }); + + it('should give an error if the timestamp is out of range', function(done) { + var db = new DB(baseConfig); + var readStream = new EventEmitter(); + db.store = { + createReadStream: sinon.stub().returns(readStream) + }; + + db.getBlockHashesByTimestamp(-1, -5, function(err, hashes) { + should.exist(err); + err.message.should.equal('Invalid Argument: timestamp out of bounds'); + done(); + }); + }); }); describe('#getPrevHash', function() {