wip
This commit is contained in:
parent
90b0e0e94b
commit
98dee7084f
@ -142,74 +142,101 @@ BlockService.prototype._blockAlreadyProcessed = function(block) {
|
||||
|
||||
};
|
||||
|
||||
/*
|
||||
The block service maintains a set of chain tips. This set includes all the block chains that have
|
||||
been created, including orphaned chains.
|
||||
|
||||
Because blocks can be delievered to us out of order, these out of order blocks enter this collection
|
||||
as new chains. We won't yet have the block's parent block.
|
||||
|
||||
This creates a unique problem. Until we get a complete chain (including the out of order blocks), we
|
||||
won't know for sure if a reorg has taken place in the unknown ancestor of the out of order blocks.
|
||||
So, we have to defer broadcaating blocks until we have a complete chain with unsent blocks.
|
||||
|
||||
*/
|
||||
BlockService.prototype._mergeBlockIntoChainTips = function(block) {
|
||||
|
||||
var prevHash = utils.reverseBufferToString(block.header.prevHash);
|
||||
|
||||
var chain = this._chainTips.get(prevHash);
|
||||
|
||||
// No matter what, our own parent can no longer be the tip of any chain
|
||||
this._chainTips.del(prevHash);
|
||||
|
||||
// This is the normal case where blocks are received in order.
|
||||
// We could still be missing blocks from our main chain if there is not a complete
|
||||
// chain between our tip and this latest block.
|
||||
if (chain) {
|
||||
chain.unshift(prevHash);
|
||||
chain.set(block.hash, chain);
|
||||
return;
|
||||
}
|
||||
|
||||
var longestChain = this._findLongestChainForHash(prevHash);
|
||||
var hasChildren = this._setChainOnTip(block.hash, chain, longestChain);
|
||||
// This is where we have an out of order block arriving,
|
||||
// but it may fill in gaps in a chain (making that chain the active one).
|
||||
// We should check for chains that have our hash listed as the last entry.
|
||||
// This means our children listed us as their parent before now, but now that
|
||||
// we have arrived, we know our parent's hadh and this may be an tip in another chain.
|
||||
// So, we put the chains together (think of playing solitaire).
|
||||
// If unificatiion is done, then this chain becomes the active one.
|
||||
var chainTips = this._attemptChainUnification(block);
|
||||
|
||||
if (!hasChildren) {
|
||||
this._chainTips.set(block.hash, chain || longestChain);
|
||||
// if we get more than one chainTip in chainTips, then we have the case where the main chain forked
|
||||
// whilst we were building orphan chains and waiting for blocks to arrive to fill in the gaps.
|
||||
// This situation should be a rare event, but can happen. This function won't determine which is the most
|
||||
// valid chain, but leave it up to others.
|
||||
|
||||
// This is the out of order condition. We can't know which chain we belong to.
|
||||
// Our hash was not referenced in any chain, therefore our parent wasn't either.
|
||||
// Only choice to make our hash the tip of its own chain into our parent arrives.
|
||||
if (chainTips.length < 1) {
|
||||
this._chainTips.set(block.hash, [prevHash]);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
BlockService.prototype._setChainOnTip = function(hash, chain, longestPrevChain) {
|
||||
|
||||
var keys = this._chainTips.keys();
|
||||
var hasChildren = false;
|
||||
|
||||
for(var i = 0; i < keys.length; i++) {
|
||||
|
||||
var key = keys[i];
|
||||
var searchChain = this._chainTips.get(key);
|
||||
|
||||
|
||||
var chainIndex = searchChain.indexOf(hash);
|
||||
|
||||
if (chainIndex > -1) {
|
||||
hasChildren = true;
|
||||
this._chainTips.set(key, searchChain.concat(chain || longestPrevChain));
|
||||
}
|
||||
}
|
||||
|
||||
return hasChildren;
|
||||
};
|
||||
|
||||
BlockService.prototype._findLongestChainForHash = function(hash) {
|
||||
|
||||
var longestChain = [];
|
||||
var keys = this._chainTips.keys();
|
||||
|
||||
for(var i = 0; i < keys.length; i++) {
|
||||
|
||||
var key = keys[i];
|
||||
var searchChain = this._chainTips.get(key);
|
||||
|
||||
assert(searchChain.length > 0, 'chain tips collection appears to be invalid');
|
||||
|
||||
var chainIndex = searchChain.indexOf(hash);
|
||||
|
||||
if (chainIndex > -1) {
|
||||
|
||||
var chain = searchChain.slice(chainIndex);
|
||||
if (chain.length > longestChain.length) {
|
||||
longestChain = chain;
|
||||
BlockService.prototype._findChainTipsWithHash = function(hash, pos) {
|
||||
// we could have more than one chain that contains this hash as the entry in position (pos)
|
||||
// (although this would be extremely rare). We should return all the chain tips that apply.
|
||||
var chainTips = [];
|
||||
this._chainTips.forEach(function(v, k) {
|
||||
if (pos === 'last') {
|
||||
if (v[v.length - 1] === hash) {
|
||||
return chainTips.push(k);
|
||||
}
|
||||
}
|
||||
if (v.indexOf(hash) > -1) {
|
||||
chainTips.push(k);
|
||||
}
|
||||
});
|
||||
return chainTips;
|
||||
};
|
||||
|
||||
/*
|
||||
The purpose of this function is to look for the opportunity for chains to unify because
|
||||
new information has arrived (a new block).
|
||||
|
||||
If we have the condition where this block's parent is the tip of a chain -and- our own
|
||||
block hash is the last block (oldest) in a chain, then unification is possible.
|
||||
It is possible to find more than one chain where our block hash is the last entry.
|
||||
In this case, each of those chains forked in blocks that came after us.
|
||||
*/
|
||||
BlockService.prototype._attemptChainUnification = function(block) {
|
||||
|
||||
var prevHash = utils.reverseBufferToString(block.header.prevHash);
|
||||
|
||||
var possibleNewChainTips = this._findChainTipWithHash(block.hash, 'last');
|
||||
var orphanChain = this._chainTips[prevHash];
|
||||
|
||||
if (orphanChain && possibleNewChainTips.length > 0) {
|
||||
for(var i = 0; i < possibleNewChainTips.length; i++) {
|
||||
var newChain = this._chainTips[possibleNewChainTips[i]];
|
||||
this.chainTips[possibleNewChainTips[i]] = newChain.concat(orphanChain);
|
||||
}
|
||||
}
|
||||
|
||||
longestChain = longestChain.length <= 1 ? [hash] : longestChain;
|
||||
return longestChain;
|
||||
return possibleNewChainTips;
|
||||
|
||||
};
|
||||
|
||||
BlockService.prototype._onBlock = function(block) {
|
||||
@ -239,19 +266,44 @@ BlockService.prototype._onBlock = function(block) {
|
||||
this.emit('reorg', block);
|
||||
break;
|
||||
default:
|
||||
var activeChainTip = this._selectActiveChain(block);
|
||||
// at this point, we know we have a complete chain containing our tip and this new block
|
||||
var activeChainTip = this._selectActiveChain();
|
||||
this._sendAllUnsentBlocksFromAcitveChain(activeChainTip);
|
||||
break;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
BlockService.prototype._selectActiveChain = function(hash) {
|
||||
// the active chain is the one that the passed in block hash has as its tip.
|
||||
// there can only be one without there being a ambiguous situation.
|
||||
// If more than one chain does have the latest incoming block, then we have a reorg
|
||||
// situation on our hands and the active chain will be decided elsewhere
|
||||
/*
|
||||
Since blocks can arrive out of order from our trusted peer, we can't rely on the latest block
|
||||
being the tip of the main/active chain. We should, instead, take the chain with the most work completed (the heaviest).
|
||||
We need not concern ourselves whether or not the block is valid, we trust our peer to do this validation.
|
||||
*/
|
||||
BlockService.prototype._selectActiveChain = function() {
|
||||
|
||||
var chainTip;
|
||||
var mostChainWork = 0;
|
||||
|
||||
this._chainTips.forEach(function(v, k) {
|
||||
var work = this._computeChainWork(k);
|
||||
if (work > mostChainWork) {
|
||||
mostChainWork = work;
|
||||
chainTip = k;
|
||||
}
|
||||
});
|
||||
|
||||
return chainTip;
|
||||
|
||||
};
|
||||
|
||||
|
||||
BlockService.prototype._computeChainWork = function(chainTip) {
|
||||
//for super old forks that have cycled out of our cache, just return zero work
|
||||
var blockHeader = this._blockHeaderQueue.get(chainTip);
|
||||
if (!blockHeader) {
|
||||
return 0;
|
||||
}
|
||||
blockHeader.chainwork;
|
||||
};
|
||||
|
||||
BlockService.prototype._getAllUnsentBlocksFromActiveChain = function(block) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user