wip
This commit is contained in:
parent
58a5c14cdf
commit
73687b36cf
@ -7,8 +7,6 @@ var Encoding = require('./encoding');
|
|||||||
var index = require('../../');
|
var index = require('../../');
|
||||||
var log = index.log;
|
var log = index.log;
|
||||||
var BufferUtil = bitcore.util.buffer;
|
var BufferUtil = bitcore.util.buffer;
|
||||||
var Reorg = require('./reorg');
|
|
||||||
var async = require('async');
|
|
||||||
var LRU = require('lru-cache');
|
var LRU = require('lru-cache');
|
||||||
var utils = require('../../utils');
|
var utils = require('../../utils');
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
@ -29,7 +27,7 @@ var BlockService = function(options) {
|
|||||||
return n.length * (1 * 1024 * 1024); // 50 MB of blocks
|
return n.length * (1 * 1024 * 1024); // 50 MB of blocks
|
||||||
}
|
}
|
||||||
}); // header -> block
|
}); // 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);
|
inherits(BlockService, BaseService);
|
||||||
@ -194,8 +192,8 @@ BlockService.prototype.getBlocks = function(startHash, endHash) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (lockedOut) {
|
if (lockedOut) {
|
||||||
return false;
|
|
||||||
log.debug('Block Service: getBlocks called, but service is still in a lock out period.');
|
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);
|
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) {
|
BlockService.prototype._onBlock = function(block) {
|
||||||
|
|
||||||
// 1. have we already seen this block?
|
// 1. have we already seen this block?
|
||||||
@ -278,13 +339,16 @@ BlockService.prototype._onBlock = function(block) {
|
|||||||
// 3. store the block for safe keeping
|
// 3. store the block for safe keeping
|
||||||
this._cacheBlock(block);
|
this._cacheBlock(block);
|
||||||
|
|
||||||
// 4. determine block state -- see the function for details about block state
|
// 4. merge this block into the set of chain tips
|
||||||
var blockSteta = this._determineBlockState(block);
|
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) {
|
switch (blockState) {
|
||||||
case 'orphaned':
|
case 'orphaned':
|
||||||
this._handleOrphanedBlock(block);
|
this._queueOrphanedBlock(block);
|
||||||
break;
|
break;
|
||||||
case 'reorg':
|
case 'reorg':
|
||||||
this.emit('reorg', block);
|
this.emit('reorg', block);
|
||||||
@ -292,11 +356,15 @@ BlockService.prototype._onBlock = function(block) {
|
|||||||
default:
|
default:
|
||||||
this.setTip(block);
|
this.setTip(block);
|
||||||
this._broadcast(this.subscriptions.blocks, 'block/block', block);
|
this._broadcast(this.subscriptions.blocks, 'block/block', block);
|
||||||
|
// check to see if we can broadcast, previously orphaned blocks on the main chain.
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
BlockService.prototype._setTip = function() {
|
||||||
|
};
|
||||||
|
|
||||||
BlockService.prototype._determineBlockState = function(block) {
|
BlockService.prototype._determineBlockState = function(block) {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -328,7 +396,7 @@ BlockService.prototype._determineBlockState = function(block) {
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (this._hasBlockArrivedOutOfOrder(block)) {
|
if (this._isOrphanBlock(block)) {
|
||||||
return 'orphaned';
|
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);
|
var prevHash = utils.reverseBufferToString(block.header.prevHash);
|
||||||
return !this._blockHeaderQueue.get(prevHash);
|
return !this._blockHeaderQueue.get(prevHash);
|
||||||
|
|
||||||
@ -349,15 +419,13 @@ BlockService.prototype._hasBlockArrivedOutOfOrder = function(block) {
|
|||||||
|
|
||||||
BlockService.prototype._isChainReorganizing = function(block) {
|
BlockService.prototype._isChainReorganizing = function(block) {
|
||||||
|
|
||||||
var prevHash = utils.reverseBufferToString(block.header.prevHash);
|
if (!this._isOrphanBlock()) {
|
||||||
return prevHash !== this.tip.hash
|
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) {
|
BlockService.prototype._broadcast = function(subscribers, name, entity) {
|
||||||
@ -369,8 +437,14 @@ BlockService.prototype._broadcast = function(subscribers, name, entity) {
|
|||||||
BlockService.prototype._cacheBlock = function(block) {
|
BlockService.prototype._cacheBlock = function(block) {
|
||||||
|
|
||||||
log.debug('Setting block: ' + block.hash + ' in the block cache.');
|
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);
|
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);
|
var operations = this._getBlockOperations(block);
|
||||||
|
|
||||||
this._db.batch(operations, function(err) {
|
this._db.batch(operations, function(err) {
|
||||||
@ -439,13 +513,13 @@ BlockService.prototype._onDbError = function(err) {
|
|||||||
|
|
||||||
|
|
||||||
BlockService.prototype._isGetP2PBlocksLockedOut = function() {
|
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()) {
|
if (!!this._isGetP2PBlocksLockedOut()) {
|
||||||
this._previousdGetP2PBlocksLockTime = Date.now();
|
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) {
|
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;
|
//log.error('A common ancestor block between hash: ' + this.tip.hash + ' (our current tip) and: ' +
|
||||||
self._printTipInfo('Reorg detected!');
|
// block.hash + ' (the forked block) could not be found.');
|
||||||
|
|
||||||
self.reorg = true;
|
//this.node.stop();
|
||||||
self.emit('reorg');
|
|
||||||
|
|
||||||
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) {
|
//var reorg = new Reorg(self.node, self);
|
||||||
log.error('Reorg failed! ' + err);
|
|
||||||
self.node.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
self._printTipInfo('Reorg successful!');
|
//reorg.handleReorg(hash, function(err) {
|
||||||
self.reorg = false;
|
|
||||||
self.cleanupAfterReorg(callback);
|
|
||||||
|
|
||||||
});
|
// if(err) {
|
||||||
|
// log.error('Reorg failed! ' + err);
|
||||||
|
// self.node.stop();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// self._printTipInfo('Reorg successful!');
|
||||||
|
// self.reorg = false;
|
||||||
|
// self.cleanupAfterReorg(callback);
|
||||||
|
|
||||||
|
//});
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
72
test/services/block/index.unit.js
Normal file
72
test/services/block/index.unit.js
Normal 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']);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user