Split out _syncBitcoindAncestor from _syncBitcoindRewind and added unit tests
This commit is contained in:
parent
9eda30ae2b
commit
ab4addc82e
133
lib/node.js
133
lib/node.js
@ -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
1
test/data/hashes.json
Normal file
File diff suppressed because one or more lines are too long
@ -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({});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user