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) {
|
BlockService.prototype._mergeBlockIntoChainTips = function(block) {
|
||||||
|
|
||||||
var prevHash = utils.reverseBufferToString(block.header.prevHash);
|
var prevHash = utils.reverseBufferToString(block.header.prevHash);
|
||||||
|
|
||||||
var chain = this._chainTips.get(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._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) {
|
if (chain) {
|
||||||
chain.unshift(prevHash);
|
chain.unshift(prevHash);
|
||||||
|
chain.set(block.hash, chain);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var longestChain = this._findLongestChainForHash(prevHash);
|
// This is where we have an out of order block arriving,
|
||||||
var hasChildren = this._setChainOnTip(block.hash, chain, longestChain);
|
// 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) {
|
// if we get more than one chainTip in chainTips, then we have the case where the main chain forked
|
||||||
this._chainTips.set(block.hash, chain || longestChain);
|
// 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) {
|
BlockService.prototype._findChainTipsWithHash = function(hash, pos) {
|
||||||
|
// we could have more than one chain that contains this hash as the entry in position (pos)
|
||||||
var keys = this._chainTips.keys();
|
// (although this would be extremely rare). We should return all the chain tips that apply.
|
||||||
var hasChildren = false;
|
var chainTips = [];
|
||||||
|
this._chainTips.forEach(function(v, k) {
|
||||||
for(var i = 0; i < keys.length; i++) {
|
if (pos === 'last') {
|
||||||
|
if (v[v.length - 1] === hash) {
|
||||||
var key = keys[i];
|
return chainTips.push(k);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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 possibleNewChainTips;
|
||||||
return longestChain;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
BlockService.prototype._onBlock = function(block) {
|
BlockService.prototype._onBlock = function(block) {
|
||||||
@ -239,19 +266,44 @@ BlockService.prototype._onBlock = function(block) {
|
|||||||
this.emit('reorg', block);
|
this.emit('reorg', block);
|
||||||
break;
|
break;
|
||||||
default:
|
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);
|
this._sendAllUnsentBlocksFromAcitveChain(activeChainTip);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
BlockService.prototype._selectActiveChain = function(hash) {
|
/*
|
||||||
// the active chain is the one that the passed in block hash has as its tip.
|
Since blocks can arrive out of order from our trusted peer, we can't rely on the latest block
|
||||||
// there can only be one without there being a ambiguous situation.
|
being the tip of the main/active chain. We should, instead, take the chain with the most work completed (the heaviest).
|
||||||
// If more than one chain does have the latest incoming block, then we have a reorg
|
We need not concern ourselves whether or not the block is valid, we trust our peer to do this validation.
|
||||||
// situation on our hands and the active chain will be decided elsewhere
|
*/
|
||||||
|
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) {
|
BlockService.prototype._getAllUnsentBlocksFromActiveChain = function(block) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user