Merge pull request #219 from pnagurny/feature/timestamp-blocks
Get block hashes by timestamp range
This commit is contained in:
commit
4343b90de2
@ -32,3 +32,14 @@ node.getBlock(blockHash, function(err, block) {
|
|||||||
//...
|
//...
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Get Block Hashes by Timestamp Range
|
||||||
|
|
||||||
|
```js
|
||||||
|
var newest = 1441914000; // Notice time is in seconds not milliseconds
|
||||||
|
var oldest = 1441911000;
|
||||||
|
|
||||||
|
node.getBlockHashesByTimestamp(newest, oldest, function(err, hashes) {
|
||||||
|
//...
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|||||||
@ -35,8 +35,8 @@ AddressService.dependencies = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
AddressService.PREFIXES = {
|
AddressService.PREFIXES = {
|
||||||
OUTPUTS: new Buffer('32', 'hex'),
|
OUTPUTS: new Buffer('02', 'hex'),
|
||||||
SPENTS: new Buffer('33', 'hex')
|
SPENTS: new Buffer('03', 'hex')
|
||||||
};
|
};
|
||||||
|
|
||||||
AddressService.SPACER_MIN = new Buffer('00', 'hex');
|
AddressService.SPACER_MIN = new Buffer('00', 'hex');
|
||||||
|
|||||||
@ -60,6 +60,10 @@ util.inherits(DB, Service);
|
|||||||
|
|
||||||
DB.dependencies = ['bitcoind'];
|
DB.dependencies = ['bitcoind'];
|
||||||
|
|
||||||
|
DB.PREFIXES = {
|
||||||
|
BLOCKS: new Buffer('01', 'hex')
|
||||||
|
};
|
||||||
|
|
||||||
DB.prototype._setDataPath = function() {
|
DB.prototype._setDataPath = function() {
|
||||||
$.checkState(this.node.datadir, 'Node is expected to have a "datadir" property');
|
$.checkState(this.node.datadir, 'Node is expected to have a "datadir" property');
|
||||||
var regtest = Networks.get('regtest');
|
var regtest = Networks.get('regtest');
|
||||||
@ -177,6 +181,7 @@ DB.prototype.transactionHandler = function(txInfo) {
|
|||||||
DB.prototype.getAPIMethods = function() {
|
DB.prototype.getAPIMethods = function() {
|
||||||
var methods = [
|
var methods = [
|
||||||
['getBlock', this, this.getBlock, 1],
|
['getBlock', this, this.getBlock, 1],
|
||||||
|
['getBlockHashesByTimestamp', this, this.getBlockHashesByTimestamp, 2],
|
||||||
['getTransaction', this, this.getTransaction, 2],
|
['getTransaction', this, this.getTransaction, 2],
|
||||||
['getTransactionWithBlockInfo', this, this.getTransactionWithBlockInfo, 2],
|
['getTransactionWithBlockInfo', this, this.getTransactionWithBlockInfo, 2],
|
||||||
['sendTransaction', this, this.sendTransaction, 1],
|
['sendTransaction', this, this.sendTransaction, 1],
|
||||||
@ -194,6 +199,54 @@ DB.prototype.getBlock = function(hash, callback) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get block hashes between two timestamps
|
||||||
|
* @param {Number} high - high timestamp, in seconds, inclusive
|
||||||
|
* @param {Number} low - low timestamp, in seconds, inclusive
|
||||||
|
* @param {Function} callback
|
||||||
|
*/
|
||||||
|
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: lowKey,
|
||||||
|
lte: highKey,
|
||||||
|
reverse: true,
|
||||||
|
valueEncoding: 'binary',
|
||||||
|
keyEncoding: 'binary'
|
||||||
|
});
|
||||||
|
|
||||||
|
stream.on('data', function(data) {
|
||||||
|
hashes.push(self._decodeBlockIndexValue(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) {
|
DB.prototype.getTransaction = function(txid, queryMempool, callback) {
|
||||||
this.node.services.bitcoind.getTransaction(txid, queryMempool, function(err, txBuffer) {
|
this.node.services.bitcoind.getTransaction(txid, queryMempool, function(err, txBuffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -371,6 +424,13 @@ DB.prototype.runAllBlockHandlers = function(block, add, callback) {
|
|||||||
this.subscriptions.block[i].emit('block', block.hash);
|
this.subscriptions.block[i].emit('block', block.hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update block index
|
||||||
|
operations.push({
|
||||||
|
type: add ? 'put' : 'del',
|
||||||
|
key: this._encodeBlockIndexKey(block.header.timestamp),
|
||||||
|
value: this._encodeBlockIndexValue(block.hash)
|
||||||
|
});
|
||||||
|
|
||||||
async.eachSeries(
|
async.eachSeries(
|
||||||
this.node.services,
|
this.node.services,
|
||||||
function(mod, next) {
|
function(mod, next) {
|
||||||
@ -402,6 +462,21 @@ 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]);
|
||||||
|
};
|
||||||
|
|
||||||
|
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,
|
* 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.
|
* by moving backwards from the forked block until it meets the current chain.
|
||||||
|
|||||||
@ -126,13 +126,13 @@ describe('Address Service', function() {
|
|||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
operations.length.should.equal(81);
|
operations.length.should.equal(81);
|
||||||
operations[0].type.should.equal('put');
|
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[0].value.toString('hex').should.equal('41e2a49ec1c0000076a91402a61d2066d19e9e2fd348a8320b7ebd4dd3ca2b88ac');
|
||||||
operations[3].type.should.equal('put');
|
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[3].value.toString('hex').should.equal('5780f3ee54889a0717152a01abee9a32cec1b0cdf8d5537a08c7bd9eeb6bfbca00000000');
|
||||||
operations[64].type.should.equal('put');
|
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');
|
operations[64].value.toString('hex').should.equal('4147a6b00000000076a9149780ccd5356e2acc0ee439ee04e0fe69426c752888ac');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@ -149,13 +149,13 @@ describe('Address Service', function() {
|
|||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
operations.length.should.equal(81);
|
operations.length.should.equal(81);
|
||||||
operations[0].type.should.equal('del');
|
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[0].value.toString('hex').should.equal('41e2a49ec1c0000076a91402a61d2066d19e9e2fd348a8320b7ebd4dd3ca2b88ac');
|
||||||
operations[3].type.should.equal('del');
|
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[3].value.toString('hex').should.equal('5780f3ee54889a0717152a01abee9a32cec1b0cdf8d5537a08c7bd9eeb6bfbca00000000');
|
||||||
operations[64].type.should.equal('del');
|
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');
|
operations[64].value.toString('hex').should.equal('4147a6b00000000076a9149780ccd5356e2acc0ee439ee04e0fe69426c752888ac');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@ -563,7 +563,7 @@ describe('Address Service', function() {
|
|||||||
});
|
});
|
||||||
createReadStreamCallCount.should.equal(1);
|
createReadStreamCallCount.should.equal(1);
|
||||||
var data = {
|
var data = {
|
||||||
key: new Buffer('32038a213afdfc551fc658e9a2a58a86e98d69b687000000000f125dd0e50fc732d67c37b6c56be7f9dc00b6859cebf982ee2cc83ed2d604bf8700000001', 'hex'),
|
key: new Buffer('02038a213afdfc551fc658e9a2a58a86e98d69b687000000000f125dd0e50fc732d67c37b6c56be7f9dc00b6859cebf982ee2cc83ed2d604bf8700000001', 'hex'),
|
||||||
value: new Buffer('41f0de058a80000076a914038a213afdfc551fc658e9a2a58a86e98d69b68788ac', 'hex')
|
value: new Buffer('41f0de058a80000076a914038a213afdfc551fc658e9a2a58a86e98d69b68788ac', 'hex')
|
||||||
};
|
};
|
||||||
testStream.emit('data', data);
|
testStream.emit('data', data);
|
||||||
@ -611,12 +611,12 @@ describe('Address Service', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
var data1 = {
|
var data1 = {
|
||||||
key: new Buffer('32038a213afdfc551fc658e9a2a58a86e98d69b68700000543a8125dd0e50fc732d67c37b6c56be7f9dc00b6859cebf982ee2cc83ed2d604bf8700000001', 'hex'),
|
key: new Buffer('02038a213afdfc551fc658e9a2a58a86e98d69b68700000543a8125dd0e50fc732d67c37b6c56be7f9dc00b6859cebf982ee2cc83ed2d604bf8700000001', 'hex'),
|
||||||
value: new Buffer('41f0de058a80000076a914038a213afdfc551fc658e9a2a58a86e98d69b68788ac', 'hex')
|
value: new Buffer('41f0de058a80000076a914038a213afdfc551fc658e9a2a58a86e98d69b68788ac', 'hex')
|
||||||
};
|
};
|
||||||
|
|
||||||
var data2 = {
|
var data2 = {
|
||||||
key: new Buffer('32038a213afdfc551fc658e9a2a58a86e98d69b68700000543ac3b6bc2939d1a70ce04bc4f619ee32608fbff5e565c1f9b02e4eaa97959c59ae700000002', 'hex'),
|
key: new Buffer('02038a213afdfc551fc658e9a2a58a86e98d69b68700000543ac3b6bc2939d1a70ce04bc4f619ee32608fbff5e565c1f9b02e4eaa97959c59ae700000002', 'hex'),
|
||||||
value: new Buffer('40c388000000000076a914038a213afdfc551fc658e9a2a58a86e98d69b68788ac', 'hex')
|
value: new Buffer('40c388000000000076a914038a213afdfc551fc658e9a2a58a86e98d69b68788ac', 'hex')
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -369,6 +369,76 @@ 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(1441914000, 1441911000, function(err, hashes) {
|
||||||
|
should.not.exist(err);
|
||||||
|
hashes.should.deep.equal([block2.hash, block1.hash]);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
readStream.emit('data', {
|
||||||
|
key: db._encodeBlockIndexKey(block2.timestamp),
|
||||||
|
value: db._encodeBlockIndexValue(block2.hash)
|
||||||
|
});
|
||||||
|
|
||||||
|
readStream.emit('data', {
|
||||||
|
key: db._encodeBlockIndexKey(block1.timestamp),
|
||||||
|
value: db._encodeBlockIndexValue(block1.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');
|
||||||
|
});
|
||||||
|
|
||||||
|
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() {
|
describe('#getPrevHash', function() {
|
||||||
it('should return prevHash from bitcoind', function(done) {
|
it('should return prevHash from bitcoind', function(done) {
|
||||||
var db = new DB(baseConfig);
|
var db = new DB(baseConfig);
|
||||||
@ -650,10 +720,22 @@ describe('DB Service', function() {
|
|||||||
batch: sinon.stub().callsArg(1)
|
batch: sinon.stub().callsArg(1)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var block = {
|
||||||
|
hash: '00000000000000000d0aaf93e464ddeb503655a0750f8b9c6eed0bdf0ccfc863',
|
||||||
|
header: {
|
||||||
|
timestamp: 1441906365
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
it('should call blockHandler in all services and perform operations', function(done) {
|
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);
|
should.not.exist(err);
|
||||||
db.store.batch.args[0][0].should.deep.equal(['op1', 'op2', 'op3', 'op4', 'op5']);
|
var blockOp = {
|
||||||
|
type: 'put',
|
||||||
|
key: db._encodeBlockIndexKey(1441906365),
|
||||||
|
value: db._encodeBlockIndexValue('00000000000000000d0aaf93e464ddeb503655a0750f8b9c6eed0bdf0ccfc863')
|
||||||
|
};
|
||||||
|
db.store.batch.args[0][0].should.deep.equal([blockOp, 'op1', 'op2', 'op3', 'op4', 'op5']);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -663,7 +745,7 @@ describe('DB Service', function() {
|
|||||||
Service3.prototype.blockHandler = sinon.stub().callsArgWith(2, new Error('error'));
|
Service3.prototype.blockHandler = sinon.stub().callsArgWith(2, new Error('error'));
|
||||||
db.node.services.service3 = new Service3();
|
db.node.services.service3 = new Service3();
|
||||||
|
|
||||||
db.runAllBlockHandlers('block', true, function(err) {
|
db.runAllBlockHandlers(block, true, function(err) {
|
||||||
should.exist(err);
|
should.exist(err);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@ -675,7 +757,7 @@ describe('DB Service', function() {
|
|||||||
service3: new Service3()
|
service3: new Service3()
|
||||||
};
|
};
|
||||||
|
|
||||||
db.runAllBlockHandlers('block', true, function(err) {
|
db.runAllBlockHandlers(block, true, function(err) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@ -688,7 +770,7 @@ describe('DB Service', function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
db.runAllBlockHandlers('block', true, function(err) {
|
db.runAllBlockHandlers(block, true, function(err) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
});
|
});
|
||||||
}).should.throw('bitcore.ErrorInvalidArgument');
|
}).should.throw('bitcore.ErrorInvalidArgument');
|
||||||
@ -701,7 +783,7 @@ describe('DB Service', function() {
|
|||||||
db.node = {};
|
db.node = {};
|
||||||
db.node.services = {};
|
db.node.services = {};
|
||||||
var methods = db.getAPIMethods();
|
var methods = db.getAPIMethods();
|
||||||
methods.length.should.equal(5);
|
methods.length.should.equal(6);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user