Split out _syncBitcoindAncestor from _syncBitcoindRewind and added unit tests

This commit is contained in:
Braydon Fuller 2015-07-23 23:02:31 -04:00
parent 9eda30ae2b
commit ab4addc82e
3 changed files with 128 additions and 55 deletions

View File

@ -75,6 +75,62 @@ Node.prototype._loadBitcoind = function(config) {
};
/**
* This function will find the common ancestor between the current chain and a forked block,
* by moving backwards from the forked block until it meets the current chain.
* @param {Block} block - The new tip that forks the current chain.
* @param {Function} done - A callback function that is called when complete.
*/
Node.prototype._syncBitcoindAncestor = function(block, done) {
var self = this;
// The current chain of hashes will likely already be available in a cache.
self.chain.getHashes(self.chain.tip.hash, function(err, currentHashes) {
if (err) {
done(err);
}
// Create a hash map for faster lookups
var currentHashesMap = {};
var length = currentHashes.length;
for (var i = 0; i < length; i++) {
currentHashesMap[currentHashes[i]] = true;
}
var ancestorHash = block.prevHash;
// We only need to go back until we meet the main chain for the forked block
// and thus don't need to find the entire chain of hashes.
async.whilst(function() {
// Wait until the previous hash is in the current chain
return ancestorHash && !currentHashesMap[ancestorHash];
}, function(next) {
self.bitcoind.getBlockIndex(ancestorHash, function(err, blockIndex) {
if (err) {
return next(err);
}
ancestorHash = blockIndex.prevHash;
next();
});
}, function(err) {
// Hash map is no-longer needed, quickly let
// scavenging garbage collection know to cleanup
currentHashesMap = null;
if (err) {
return done(err);
} else if (!ancestorHash) {
return done(new Error('Unknown common ancestor.'));
}
done(null, ancestorHash);
});
});
};
/**
* This function will attempt to rewind the chain to the common ancestor
* between the current chain and a forked block.
@ -85,70 +141,39 @@ Node.prototype._syncBitcoindRewind = function(block, done) {
var self = this;
self.chain.getHashes(self.chain.tip.hash, function(err, currentHashes) {
if (err) {
done(err);
}
self._syncBitcoindAncestor(block, function(err, ancestorHash) {
self.chain.getHashes(block.hash, function(err, newHashes) {
if (err) {
done(err);
}
// Rewind the chain to the common ancestor
async.doWhilst(
function(removeDone) {
var ancestorHash = newHashes.pop();
var block = self.chain.tip;
// Create a hash map for faster lookups
var currentHashesMap = {};
var length = currentHashes.length;
for (var i = 0; i < length; i++) {
currentHashesMap[currentHashes[i]] = true;
}
self.getBlock(block.prevHash, function(err, previousTip) {
if (err) {
removeDone(err);
}
// Step back the chain of hashes until we find the intersection point
while (ancestorHash && !currentHashesMap[ancestorHash]) {
ancestorHash = newHashes.pop();
}
// Hash map is no-longer needed
currentHashesMap = null;
if (!ancestorHash) {
return done(new Error('Can not rewind, unknown common ancestor.'));
}
// Rewind the chain to the common ancestor
async.doWhilst(
function(removeDone) {
var block = self.chain.tip;
self.getBlock(block.prevHash, function(err, previousTip) {
self.db._onChainRemoveBlock(block, function(err) {
if (err) {
removeDone(err);
return removeDone(err);
}
self.db._onChainRemoveBlock(block, function(err) {
if (err) {
return removeDone(err);
}
delete self.chain.tip.__transactions;
var previousHeight = self.chain.tip.__height - 1;
previousTip.__height = previousHeight;
self.chain.tip = previousTip;
self.chain.saveMetadata();
self.chain.emit('removeblock', block);
removeDone();
});
delete self.chain.tip.__transactions;
var previousHeight = self.chain.tip.__height - 1;
previousTip.__height = previousHeight;
self.chain.tip = previousTip;
self.chain.saveMetadata();
self.chain.emit('removeblock', block);
removeDone();
});
}, function() {
return self.chain.tip.hash !== ancestorHash;
}, done
);
});
});
}, function() {
return self.chain.tip.hash !== ancestorHash;
}, done
);
});

1
test/data/hashes.json Normal file

File diff suppressed because one or more lines are too long

View File

@ -12,7 +12,8 @@ var proxyquire = require('proxyquire');
var chainlib = require('chainlib');
var OriginalNode = chainlib.Node;
var fs = require('fs');
var bitcoinConfBuffer = fs.readFileSync('./test/data/bitcoin.conf');
var bitcoinConfBuffer = fs.readFileSync(__dirname + '/data/bitcoin.conf');
var chainHashes = require('./data/hashes.json');
var BaseNode = function() {};
util.inherits(BaseNode, EventEmitter);
@ -69,6 +70,52 @@ describe('Bitcoind Node', function() {
should.exist(node.bitcoind);
});
});
describe('#_syncBitcoindAncestor', function() {
it('will find an ancestor 6 deep', function() {
var node = new Node({});
node.chain = {
getHashes: function(tipHash, callback) {
callback(null, chainHashes);
},
tip: {
hash: chainHashes[chainHashes.length]
}
};
var expectedAncestor = chainHashes[chainHashes.length - 6];
var forkedBlocks = {
'd7fa6f3d5b2fe35d711e6aca5530d311b8c6e45f588a65c642b8baf4b4441d82': {
prevHash: '76d920dbd83beca9fa8b2f346d5c5a81fe4a350f4b355873008229b1e6f8701a'
},
'76d920dbd83beca9fa8b2f346d5c5a81fe4a350f4b355873008229b1e6f8701a': {
prevHash: 'f0a0d76a628525243c8af7606ee364741ccd5881f0191bbe646c8a4b2853e60c'
},
'f0a0d76a628525243c8af7606ee364741ccd5881f0191bbe646c8a4b2853e60c': {
prevHash: '2f72b809d5ccb750c501abfdfa8c4c4fad46b0b66c088f0568d4870d6f509c31'
},
'2f72b809d5ccb750c501abfdfa8c4c4fad46b0b66c088f0568d4870d6f509c31': {
prevHash: 'adf66e6ae10bc28fc22bc963bf43e6b53ef4429269bdb65038927acfe66c5453'
},
'adf66e6ae10bc28fc22bc963bf43e6b53ef4429269bdb65038927acfe66c5453': {
prevHash: '3ea12707e92eed024acf97c6680918acc72560ec7112cf70ac213fb8bb4fa618'
},
'3ea12707e92eed024acf97c6680918acc72560ec7112cf70ac213fb8bb4fa618': {
prevHash: expectedAncestor
},
};
node.bitcoind = {
getBlockIndex: function(hash) {
return forkedBlocks[hash];
}
};
var block = forkedBlocks['d7fa6f3d5b2fe35d711e6aca5530d311b8c6e45f588a65c642b8baf4b4441d82'];
node._syncBitcoindAncestor(block, function(err, ancestorHash) {
if (err) {
throw err;
}
ancestorHash.should.equal(expectedAncestor);
});
});
});
describe('#_syncBitcoind', function() {
it('will get and add block up to the tip height', function(done) {
var node = new Node({});