This commit is contained in:
Chris Kleeschulte 2017-06-26 08:46:32 -04:00
parent 58a5c14cdf
commit 73687b36cf
2 changed files with 187 additions and 37 deletions

View File

@ -7,8 +7,6 @@ var Encoding = require('./encoding');
var index = require('../../');
var log = index.log;
var BufferUtil = bitcore.util.buffer;
var Reorg = require('./reorg');
var async = require('async');
var LRU = require('lru-cache');
var utils = require('../../utils');
var _ = require('lodash');
@ -29,7 +27,7 @@ var BlockService = function(options) {
return n.length * (1 * 1024 * 1024); // 50 MB of blocks
}
}); // header -> block
this._orphanedBlockList = []; // this is a list of block hashes for which one or more ancestor blocks has not yet arrived
this._chainTips = LRU(50); // chain tip -> [ tip-1 hash, tip-2 hash, tip-N hash]
};
inherits(BlockService, BaseService);
@ -194,8 +192,8 @@ BlockService.prototype.getBlocks = function(startHash, endHash) {
}
if (lockedOut) {
return false;
log.debug('Block Service: getBlocks called, but service is still in a lock out period.');
return false;
}
};
@ -265,6 +263,69 @@ BlockService.prototype._blockAlreadyProcessed = function(block) {
return this._blockHeaderQueue.get(block.hash);
};
BlockService.prototype._mergeBlockIntoChainTips = function(block) {
function getPrevHashChain(keys, hash) {
for(var i = 0; i < keys.length; i++) {
console.log('here');
var key = keys[i];
var searchChain = this._chainTips.get(key);
console.log(searchChain);
var chainIndex = searchChain.indexOf(hash);
if (chainIndex > -1) {
return searchChain.slice(chainIndex);
}
}
}
var prevHash = utils.reverseBufferToString(block.header.prevHash);
var hasChildren = false;
var chain = this._chainTips.get(prevHash);
if (chain) {
chain.unshift(prevHash);
}
var keys = this._chainTips.keys();
// looking for chains where this block is an ancestor of the tip of the chain
for(var i = 0; i < keys.length; i++) {
var key = keys[i];
var searchChain = this._chainTips.get(key);
var chainIndex = searchChain.indexOf(block.hash);
if (chainIndex > -1) {
hasChildren = true;
var newChain = searchChain.concat(chain || getPrevHashChain(keys, prevHash));
this._chainTips.set(key, newChain);
}
}
if (chain && !hasChildren) {
this._chainTips.set(block.hash, chain);
}
this._chainTips.del(prevHash);
// if we have don't have any parents or children in chainTips, then create a new chain with this block
if (!hasChildren && !chain) {
this._chainTips.set(block.hash, getPrevHashChain(keys, prevHash));
}
};
BlockService.prototype._onBlock = function(block) {
// 1. have we already seen this block?
@ -278,13 +339,16 @@ BlockService.prototype._onBlock = function(block) {
// 3. store the block for safe keeping
this._cacheBlock(block);
// 4. determine block state -- see the function for details about block state
var blockSteta = this._determineBlockState(block);
// 4. merge this block into the set of chain tips
this._mergeBlockIntoChainTips(block);
// 5. react to state of block
// 5. determine block state, reorg, orphaned, normal
var blockState = this._determineBlockState(block);
// 6. react to state of block
switch (blockState) {
case 'orphaned':
this._handleOrphanedBlock(block);
this._queueOrphanedBlock(block);
break;
case 'reorg':
this.emit('reorg', block);
@ -292,11 +356,15 @@ BlockService.prototype._onBlock = function(block) {
default:
this.setTip(block);
this._broadcast(this.subscriptions.blocks, 'block/block', block);
// check to see if we can broadcast, previously orphaned blocks on the main chain.
break;
}
};
BlockService.prototype._setTip = function() {
};
BlockService.prototype._determineBlockState = function(block) {
/*
@ -328,7 +396,7 @@ BlockService.prototype._determineBlockState = function(block) {
*/
if (this._hasBlockArrivedOutOfOrder(block)) {
if (this._isOrphanBlock(block)) {
return 'orphaned';
}
@ -340,8 +408,10 @@ BlockService.prototype._determineBlockState = function(block) {
};
BlockService.prototype._hasBlockArrivedOutOfOrder = function(block) {
BlockService.prototype._isOrphanBlock = function(block) {
// all blocks that we've seen before should be in the blockHeaderQueue, if this block's prev
// hash isn't, then we definitely have an orphan block.
var prevHash = utils.reverseBufferToString(block.header.prevHash);
return !this._blockHeaderQueue.get(prevHash);
@ -349,15 +419,13 @@ BlockService.prototype._hasBlockArrivedOutOfOrder = function(block) {
BlockService.prototype._isChainReorganizing = function(block) {
var prevHash = utils.reverseBufferToString(block.header.prevHash);
return prevHash !== this.tip.hash
if (!this._isOrphanBlock()) {
var prevHash = utils.reverseBufferToString(block.header.prevHash);
return prevHash !== this.tip.hash;
}
};
return false;
BlockService.prototypee._findCommonAncestor = function(tipHash, forkedHash) {
// find the common ancestor between these 2 hashes
// most likely, we will already have the block headers
};
BlockService.prototype._broadcast = function(subscribers, name, entity) {
@ -369,8 +437,14 @@ BlockService.prototype._broadcast = function(subscribers, name, entity) {
BlockService.prototype._cacheBlock = function(block) {
log.debug('Setting block: ' + block.hash + ' in the block cache.');
// 1. set the block queue, which holds full blocks in memory
this._blockQueue.set(block.hash, block);
// 2. set the block header queue, which holds hash -> header -and- height -> headeer in memory
this._blockHeader.set(block.hash, block.header.toObject()); // we don't know about the height yet
// 3. store the block in the database
var operations = this._getBlockOperations(block);
this._db.batch(operations, function(err) {
@ -439,13 +513,13 @@ BlockService.prototype._onDbError = function(err) {
BlockService.prototype._isGetP2PBlocksLockedOut = function() {
return Date.now() < (self._getP2PBlocksLockoutPeriod + (self._previousdGetP2PBlocksLockTime || 0));
return Date.now() < (this._getP2PBlocksLockoutPeriod + (this._previousdGetP2PBlocksLockTime || 0));
};
BlockService.prototype._getP2PBlocks = function(block) {
BlockService.prototype._getP2PBlocks = function() {
if (!!this._isGetP2PBlocksLockedOut()) {
this._previousdGetP2PBlocksLockTime = Date.now();
self._p2p.getBlocks({ startHash: startHash });
this._p2p.getBlocks({ startHash: startHash });
}
};
@ -557,31 +631,35 @@ BlockService.prototype._getBlockOperations = function(obj) {
};
BlockService.prototype._handleReorg = function(hash, callback) {
log.error('A common ancestor block between hash: ' + this.tip.hash + ' (our current tip) and: ' +
block.hash + ' (the forked block) could not be found.');
this.node.stop();
// 1. log out that we are in a reorg state.
//log.warn('Chain reorganization detected! Our current block tip is: this._tip.hash ' +
var self = this;
self._printTipInfo('Reorg detected!');
//log.error('A common ancestor block between hash: ' + this.tip.hash + ' (our current tip) and: ' +
// block.hash + ' (the forked block) could not be found.');
self.reorg = true;
self.emit('reorg');
//this.node.stop();
var reorg = new Reorg(self.node, self);
//var self = this;
//self._printTipInfo('Reorg detected!');
reorg.handleReorg(hash, function(err) {
//self.reorg = true;
//self.emit('reorg');
if(err) {
log.error('Reorg failed! ' + err);
self.node.stop();
}
//var reorg = new Reorg(self.node, self);
self._printTipInfo('Reorg successful!');
self.reorg = false;
self.cleanupAfterReorg(callback);
//reorg.handleReorg(hash, function(err) {
});
// if(err) {
// log.error('Reorg failed! ' + err);
// self.node.stop();
// }
// self._printTipInfo('Reorg successful!');
// self.reorg = false;
// self.cleanupAfterReorg(callback);
//});
};

View File

@ -0,0 +1,72 @@
'use strict';
var expect = require('chai').expect;
var BlockService = require('../../../lib/services/block');
var LRU = require('lru-cache');
describe('Block Service', function() {
var blockService;
beforeEach(function() {
blockService = new BlockService({ node: { services: []}});
blockService._chainTips = LRU(50);
});
describe('Chain Tips', function() {
it('should merge blocks into chain tips using active chain blocks only' , function() {
var blocks = ['aa','bb','cc','dd','ee'];
blocks.forEach(function(n, index) {
var buf = new Buffer('00', 'hex');
if (index) {
buf = new Buffer(blocks[index-1], 'hex');
}
var block = { header: { prevHash: buf }, hash: n };
blockService._mergeBlockIntoChainTips(block);
});
expect(blockService._chainTips.length).to.equal(1);
expect(blockService._chainTips.get('ee')).to.deep.equal(['dd', 'cc', 'bb', 'aa', '00']);
});
it('should merge blocks into chain tips using out of order blocks' , function() {
var blocks = ['ee','aa','bb','dd','cc'];
var prevBlocks = ['dd','00','aa','cc','bb'];
blocks.forEach(function(n, index) {
var block = { header: { prevHash: new Buffer(prevBlocks[index], 'hex') }, hash: n };
blockService._mergeBlockIntoChainTips(block);
});
expect(blockService._chainTips.length).to.equal(1);
expect(blockService._chainTips.get('ee')).to.deep.equal(['dd', 'cc', 'bb', 'aa', '00']);
});
it('should merge chain tips where there is a fork (a parent block has more than one child)' , function() {
var blocks = ['aa','bb','cc','dd','ee'];
var prevBlocks = ['00','aa','aa','cc','dd'];
blocks.forEach(function(n, index) {
var block = { header: { prevHash: new Buffer(prevBlocks[index], 'hex') }, hash: n };
blockService._mergeBlockIntoChainTips(block);
});
expect(blockService._chainTips.length).to.equal(2);
expect(blockService._chainTips.get('ee')).to.deep.equal(['dd', 'cc', 'aa', '00']);
});
});
});