This commit is contained in:
Chris Kleeschulte 2017-05-23 08:53:54 -04:00
parent 6fc170f7da
commit b51179274f
8 changed files with 204 additions and 111 deletions

View File

@ -111,6 +111,10 @@ BlockHandler.prototype._setupStreams = function() {
.pipe(processSerial); .pipe(processSerial);
processSerial.on('finish', self._onFinish.bind(self)); processSerial.on('finish', self._onFinish.bind(self));
self.block.once('reorg', function() {
blockStream.push(null);
});
}; };
BlockHandler.prototype._onFinish = function() { BlockHandler.prototype._onFinish = function() {
@ -172,10 +176,6 @@ BlockStream.prototype._process = function() {
self.block.getBlocks(blockArgs, function(err, blocks) { self.block.getBlocks(blockArgs, function(err, blocks) {
if(err) { if(err) {
if (err === 'reorg') {
self.push(null);
return next();
}
return next(err); return next(err);
} }
@ -208,7 +208,7 @@ BlockStream.prototype._pushBlocks = function(blocks) {
for(var i = 0; i < blocks.length; i++) { for(var i = 0; i < blocks.length; i++) {
self.lastEmittedHash = blocks[i].hash; self.lastEmittedHash = blocks[i].hash;
log.debug('Pushing block: ' + blocks[i].hash + ' from the blockstream'); log.info('Pushing block: ' + blocks[i].hash + ' from the blockstream');
self.push(blocks[i]); self.push(blocks[i]);
} }

View File

@ -100,36 +100,6 @@ BlockService.prototype.getNetworkTipHash = function() {
return this.bitcoind.tiphash; return this.bitcoind.tiphash;
}; };
BlockService.prototype._getBlockOperations = function(obj) {
var self = this;
if (_.isArray(obj)) {
var ops = [];
_.forEach(obj, function(block) {
ops.push(self._getBlockOperations(block));
});
return _.flatten(ops);
}
var operations = [];
operations.push({
type: 'put',
key: self.encoding.encodeBlockHashKey(obj.hash),
value: self.encoding.encodeBlockHeightValue(obj.height)
});
operations.push({
type: 'put',
key: self.encoding.encodeBlockHeightKey(obj.height),
value: self.encoding.encodeBlockHashValue(obj.hash)
});
return operations;
};
BlockService.prototype.getBlockOperations = function(block, add, type, callback) { BlockService.prototype.getBlockOperations = function(block, add, type, callback) {
var operations = []; var operations = [];
@ -210,7 +180,9 @@ BlockService.prototype.getBlocks = function(blockArgs, callback) {
return next(null, self.genesis); return next(null, self.genesis);
} }
next(null, self._rawBlockQueue.get(blockArg)); var cachedBlock = self._rawBlockQueue.get(blockArg);
console.log(cachedBlock);
next(null);
}, },
function(block, next) { function(block, next) {
@ -374,7 +346,7 @@ BlockService.prototype._getBlocks = function(callback) {
return callback(err); return callback(err);
} }
log.debug('Completed syncing block headers from the network.'); log.info('Completed syncing block headers from the network.');
callback(null, blocksDiff); callback(null, blocksDiff);
}); });
@ -423,7 +395,7 @@ BlockService.prototype._setHandlers = function() {
}); });
self._blockHandler.on('synced', function() { self._blockHandler.on('synced', function() {
log.debug('Synced: ' + self.tip.hash); log.info('Synced: ' + self.tip.hash);
self._startSubscriptions(); self._startSubscriptions();
}); });
}; };
@ -439,9 +411,6 @@ BlockService.prototype._detectStartupReorg = function(callback) {
self.getBlockHeader(hash, function(err, header) { self.getBlockHeader(hash, function(err, header) {
if (err) { if (err) {
if (err.code === -5) {
return callback();
}
return callback(err); return callback(err);
} }
@ -449,7 +418,7 @@ BlockService.prototype._detectStartupReorg = function(callback) {
return callback(); return callback();
} }
return self._handleReorg(header.hash, callback); self._handleReorg(header.hash, callback);
}); });
}; };
@ -460,6 +429,7 @@ BlockService.prototype._handleReorg = function(hash, callback) {
self.printTipInfo('Reorg detected!'); self.printTipInfo('Reorg detected!');
self.reorg = true; self.reorg = true;
self.emit('reorg');
var reorg = new Reorg(self.node, self); var reorg = new Reorg(self.node, self);
@ -666,4 +636,35 @@ BlockService.prototype._getReorgOperations = function(hash, height) {
}; };
BlockService.prototype._getBlockOperations = function(obj) {
var self = this;
if (_.isArray(obj)) {
var ops = [];
_.forEach(obj, function(block) {
ops.push(self._getBlockOperations(block));
});
return _.flatten(ops);
}
var operations = [];
operations.push({
type: 'put',
key: self.encoding.encodeBlockHashKey(obj.hash),
value: self.encoding.encodeBlockHeightValue(obj.height)
});
operations.push({
type: 'put',
key: self.encoding.encodeBlockHeightKey(obj.height),
value: self.encoding.encodeBlockHashValue(obj.hash)
});
return operations;
};
module.exports = BlockService; module.exports = BlockService;

View File

@ -5,19 +5,20 @@ var BaseService = require('../../service');
var inherits = require('util').inherits; var inherits = require('util').inherits;
var LRU = require('lru-cache'); var LRU = require('lru-cache');
var utils = require('../../../lib/utils'); var utils = require('../../../lib/utils');
var bitcore = require('bitcore-lib');
function TimestampService(options) { function TimestampService(options) {
BaseService.call(this, options); BaseService.call(this, options);
this.currentBlock = null; this.currentBlock = null;
this.currentTimestamp = null; this.currentTimestamp = null;
this._cache = LRU(50); this._cache = LRU(50);
var genesis = self.node.services.block.genesis; this._cache.set(new Array(65).join('0'), { time: 0 });
this._cache.set(genesis.hash, genesis.__height); this._setHandlers();
} }
inherits(TimestampService, BaseService); inherits(TimestampService, BaseService);
TimestampService.dependencies = [ 'db' ]; TimestampService.dependencies = [ 'db', 'block' ];
TimestampService.prototype.start = function(callback) { TimestampService.prototype.start = function(callback) {
var self = this; var self = this;
@ -34,13 +35,16 @@ TimestampService.prototype.start = function(callback) {
callback(); callback();
}); });
self.counter = 0;
}; };
TimestampService.prototype.stop = function(callback) { TimestampService.prototype.stop = function(callback) {
setImmediate(callback); setImmediate(callback);
}; };
TimestampService.prototype._setHandlers = function() {
var self = this;
};
TimestampService.prototype._processBlockHandlerQueue = function(block) { TimestampService.prototype._processBlockHandlerQueue = function(block) {
var self = this; var self = this;
@ -54,7 +58,7 @@ TimestampService.prototype._processBlockHandlerQueue = function(block) {
if (prev && !prev.prevHash) { if (prev && !prev.prevHash) {
if (blockTime <= prev.time) { if (blockTime <= prev.time) {
blockTime++; blockTime = prev.time + 1;
} }
self._cache.del(prevHash); self._cache.del(prevHash);
@ -89,58 +93,60 @@ TimestampService.prototype.blockHandler = function(block, connectBlock, callback
var operations = []; var operations = [];
if (!queue.length < 1) { if (queue.length === 0) {
return callback(null, []); return callback(null, queue);
} }
operations = operations.concat([ for(var i = 0; i < queue.length; i++) {
{
type: action, var item = queue[i];
key: self.encoding.encodeTimestampBlockKey(timestamp), operations = operations.concat([
value: self.encoding.encodeTimestampBlockValue(block.header.hash) {
}, type: action,
{ key: self.encoding.encodeTimestampBlockKey(item.time),
type: action, value: self.encoding.encodeTimestampBlockValue(item.hash)
key: self.encoding.encodeBlockTimestampKey(block.header.hash), },
value: self.encoding.encodeBlockTimestampValue(timestamp) {
} type: action,
]); key: self.encoding.encodeBlockTimestampKey(item.hash),
value: self.encoding.encodeBlockTimestampValue(item.time)
}
]);
}
callback(null, operations); callback(null, operations);
}; };
TimestampService.prototype.getBlockHeights = function(timestamps, callback) { TimestampService.prototype.getTimestamp = function(hash, callback) {
this._getValue(hash, callback);
};
TimestampService.prototype.getHash = function(timestamp, callback) {
this._getValue(timestamp, callback);
};
TimestampService.prototype._getValue = function(key, callback) {
var self = this; var self = this;
timestamps.sort(); var keyBuf, fn;
timestamps = timestamps.map(function(timestamp) {
return timestamp >= MAINNET_BITCOIN_GENESIS_TIME ? timestamp : MAINNET_BITCOIN_GENESIS_TIME;
});
var start = self.encoding.encodeTimestampBlockKey(timestamps[0]);
var end = self.encoding.encodeTimestampBlockKey(timestamps[1]);
var stream = self.db.createReadStream({
gte: start,
lte: end
});
var hashes = []; if (key.length === 64){
var hashTuple = []; keyBuf = self.encoding.encodeBlockTimestampKey(key);
var streamErr = null; fn = self.encoding.decodeBlockTimestampValue;
} else {
keyBuf = self.encoding.encodeTimestampBlockKey(key);
fn = self.encoding.decodeTimestampBlockValue;
}
stream.on('data', function(data) { self.db.get(keyBuf, function(err, value) {
hashes.push(self.encoding.decodeTimestampBlockValue(data.value));
});
stream.on('error', function(err) { if (err) {
streamErr = err; return callback(err);
});
stream.on('end', function() {
if (!streamErr && hashes.length > 1) {
hashTuple = [ hashes[0], hashes[hashes.length - 1] ];
} }
callback(streamErr, hashTuple);
callback(null, fn(value));
}); });
}; };
module.exports = TimestampService; module.exports = TimestampService;

View File

@ -282,7 +282,6 @@ WebService.prototype.transformHttpsOptions = function() {
WebService.prototype._endpointGetInfo = function() { WebService.prototype._endpointGetInfo = function() {
var self = this; var self = this;
return function(req, res) { return function(req, res) {
console.log(self.node.services);
res.jsonp({ res.jsonp({
result: 'ok', result: 'ok',
dbheight: self.node.services.block.tip.__height, dbheight: self.node.services.block.tip.__height,

View File

@ -49,6 +49,7 @@ var bitcore = {
'db', 'db',
'web', 'web',
'block', 'block',
'timestamp',
'block-test' 'block-test'
], ],
servicesConfig: { servicesConfig: {
@ -118,7 +119,7 @@ describe('Block Operations', function() {
async.timesLimit(opts.initialHeight, 12, function(n, next) { async.timesLimit(opts.initialHeight, 12, function(n, next) {
utils.queryBitcoreNode(Object.assign({ utils.queryBitcoreNode(Object.assign({
path: '/test/hash/' + n path: '/test/block/hash/' + n
}, bitcore.httpOpts), function(err, res) { }, bitcore.httpOpts), function(err, res) {
if(err) { if(err) {
@ -143,7 +144,7 @@ describe('Block Operations', function() {
it('should sync block heights as keys and hashes as values', function(done) { it('should sync block heights as keys and hashes as values', function(done) {
async.timesLimit(opts.initialHeight, 12, function(n, next) { async.timesLimit(opts.initialHeight, 12, function(n, next) {
utils.queryBitcoreNode(Object.assign({ utils.queryBitcoreNode(Object.assign({
path: '/test/height/' + self.hashes[n] path: '/test/block/height/' + self.hashes[n]
}, bitcore.httpOpts), function(err, res) { }, bitcore.httpOpts), function(err, res) {
if(err) { if(err) {

View File

@ -43,7 +43,8 @@ var bitcore = {
'db', 'db',
'web', 'web',
'block', 'block',
'reorg-test' 'reorg-test',
'timestamp'
], ],
servicesConfig: { servicesConfig: {
bitcoind: { bitcoind: {

View File

@ -9,7 +9,7 @@ var TestWebService = function(options) {
inherits(TestWebService, BaseService); inherits(TestWebService, BaseService);
TestWebService.dependencies = ['web', 'block']; TestWebService.dependencies = ['web', 'block', 'timestamp'];
TestWebService.prototype.start = function(callback) { TestWebService.prototype.start = function(callback) {
callback(); callback();
@ -23,18 +23,30 @@ TestWebService.prototype.setupRoutes = function(app) {
var self = this; var self = this;
app.get('/hash/:height', function(req, res) { app.get('/block/hash/:height', function(req, res) {
self.node.services.block.getBlockHash(req.params.height, function(err, hash) { self.node.services.block.getBlockHash(req.params.height, function(err, hash) {
res.status(200).jsonp({ hash: hash, height: parseInt(req.params.height) }); res.status(200).jsonp({ hash: hash, height: parseInt(req.params.height) });
}); });
}); });
app.get('/height/:hash', function(req, res) { app.get('/block/height/:hash', function(req, res) {
self.node.services.block.getBlockHeight(req.params.hash, function(err, height) { self.node.services.block.getBlockHeight(req.params.hash, function(err, height) {
res.status(200).jsonp({ hash: req.params.hash, height: height }); res.status(200).jsonp({ hash: req.params.hash, height: height });
}); });
}); });
app.get('/timestamp/time/:hash', function(req, res) {
self.node.services.timestamp.getTimestamp(req.params.hash, function(err, timestamp) {
res.status(200).jsonp({ hash: req.params.hash, timestamp: timestamp });
});
});
app.get('/timestamp/hash/:time', function(req, res) {
self.node.services.timestamp.getHash(req.params.time, function(err, hash) {
res.status(200).jsonp({ hash: hash, timestamp: parseInt(req.params.time) });
});
});
}; };
TestWebService.prototype.getRoutePrefix = function() { TestWebService.prototype.getRoutePrefix = function() {

View File

@ -1,12 +1,11 @@
'use strict'; 'use strict';
var chai = require('chai'); var chai = require('chai');
var should = chai.should(); var expect = chai.expect;
var async = require('async'); var async = require('async');
var BitcoinRPC = require('bitcoind-rpc'); var BitcoinRPC = require('bitcoind-rpc');
var path = require('path'); var path = require('path');
var utils = require('./utils'); var utils = require('./utils');
var crypto = require('crypto');
var debug = true; var debug = true;
var bitcoreDataDir = '/tmp/bitcore'; var bitcoreDataDir = '/tmp/bitcore';
@ -49,7 +48,9 @@ var bitcore = {
'bitcoind', 'bitcoind',
'web', 'web',
'db', 'db',
'timestamp' 'timestamp',
'block',
'test-timestamp'
], ],
servicesConfig: { servicesConfig: {
bitcoind: { bitcoind: {
@ -62,6 +63,9 @@ var bitcore = {
zmqpubrawtx: bitcoin.args.zmqpubrawtx zmqpubrawtx: bitcoin.args.zmqpubrawtx
} }
] ]
},
'test-timestamp': {
requirePath: path.resolve(__dirname + '/test_web.js')
} }
} }
} }
@ -85,16 +89,7 @@ var opts = {
bitcoinDataDir: bitcoinDataDir, bitcoinDataDir: bitcoinDataDir,
bitcoreDataDir: bitcoreDataDir, bitcoreDataDir: bitcoreDataDir,
rpc: new BitcoinRPC(rpcConfig), rpc: new BitcoinRPC(rpcConfig),
walletPassphrase: 'test',
txCount: 0,
blockHeight: 0, blockHeight: 0,
walletPrivKeys: [],
initialTxs: [],
fee: 100000,
feesReceived: 0,
satoshisSent: 0,
walletId: crypto.createHash('sha256').update('test').digest('hex'),
satoshisReceived: 0,
initialHeight: 150 initialHeight: 150
}; };
@ -112,15 +107,93 @@ describe('Timestamp Index', function() {
async.series([ async.series([
utils.startBitcoind.bind(utils, self.opts), utils.startBitcoind.bind(utils, self.opts),
utils.waitForBitcoinReady.bind(utils, self.opts), utils.waitForBitcoinReady.bind(utils, self.opts),
utils.unlockWallet.bind(utils, self.opts),
utils.sendTxs.bind(utils, self.opts),
utils.startBitcoreNode.bind(utils, self.opts), utils.startBitcoreNode.bind(utils, self.opts),
utils.waitForBitcoreNode.bind(utils, self.opts), utils.waitForBitcoreNode.bind(utils, self.opts),
function(next) {
async.timesLimit(opts.initialHeight, 12, function(n, next) {
utils.queryBitcoreNode(Object.assign({
path: '/test/block/hash/' + n
}, bitcore.httpOpts), function(err, res) {
if(err) {
return done(err);
}
res = JSON.parse(res);
expect(res.height).to.equal(n);
expect(res.hash.length).to.equal(64);
next(null, res.hash);
});
}, function(err, hashes) {
if(err) {
return next(err);
}
self.hashes = hashes;
next();
});
}
], done); ], done);
}); });
it('should sync timestamps', function(done) { it('should sync block hashes as keys and timestamps as values', function(done) {
done();
var lastTimestamp = 0;
async.mapLimit(self.hashes, 12, function(hash, next) {
utils.queryBitcoreNode(Object.assign({
path: '/test/timestamp/time/' + hash
}, bitcore.httpOpts), function(err, res) {
if(err) {
return next(err);
}
res = JSON.parse(res);
next(null, res.timestamp);
});
}, function(err, timestamps) {
if(err) {
return done(err);
}
timestamps.forEach(function(timestamp) {
expect(timestamp).to.be.above(lastTimestamp);
lastTimestamp = timestamp;
});
self.timestamps = timestamps;
done();
});
});
it('should sync block timestamps as keys and block hashes as values', function(done) {
async.eachOfLimit(self.timestamps, 12, function(timestamp, index, next) {
utils.queryBitcoreNode(Object.assign({
path: '/test/timestamp/hash/' + timestamp
}, bitcore.httpOpts), function(err, res) {
if(err) {
return done(err);
}
res = JSON.parse(res);
expect(res.hash).to.equal(self.hashes[index]);
expect(res.timestamp).to.equal(timestamp);
next();
});
}, function(err) {
if(err) {
return done(err);
}
done();
});
}); });
}); });