optimizations for handling reorgs better
This commit is contained in:
parent
a0e40ffd15
commit
60a7f5ea29
@ -48,10 +48,6 @@ function DB(options) {
|
|||||||
|
|
||||||
this._setDataPath();
|
this._setDataPath();
|
||||||
|
|
||||||
this.cache = {
|
|
||||||
hashes: {}, // dictionary of hash -> prevHash
|
|
||||||
chainHashes: {}
|
|
||||||
};
|
|
||||||
this.lastSavedMetadata = null;
|
this.lastSavedMetadata = null;
|
||||||
this.lastSavedMetadataThreshold = 0; // Set this during syncing for faster performance
|
this.lastSavedMetadataThreshold = 0; // Set this during syncing for faster performance
|
||||||
|
|
||||||
@ -133,7 +129,6 @@ DB.prototype.start = function(callback) {
|
|||||||
|
|
||||||
self.tip = tip;
|
self.tip = tip;
|
||||||
self.tip.__height = metadata.tipHeight;
|
self.tip.__height = metadata.tipHeight;
|
||||||
self.cache = metadata.cache;
|
|
||||||
self.sync();
|
self.sync();
|
||||||
self.emit('ready');
|
self.emit('ready');
|
||||||
setImmediate(callback);
|
setImmediate(callback);
|
||||||
@ -311,7 +306,6 @@ DB.prototype.saveMetadata = function(callback) {
|
|||||||
var metadata = {
|
var metadata = {
|
||||||
tip: self.tip ? self.tip.hash : null,
|
tip: self.tip ? self.tip.hash : null,
|
||||||
tipHeight: self.tip && self.tip.__height ? self.tip.__height : 0,
|
tipHeight: self.tip && self.tip.__height ? self.tip.__height : 0,
|
||||||
cache: self.cache
|
|
||||||
};
|
};
|
||||||
|
|
||||||
self.lastSavedMetadata = new Date();
|
self.lastSavedMetadata = new Date();
|
||||||
@ -406,76 +400,6 @@ DB.prototype.runAllBlockHandlers = function(block, add, callback) {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Will get an array of hashes all the way to the genesis block for
|
|
||||||
* the chain based on "block hash" as the tip.
|
|
||||||
*
|
|
||||||
* @param {String} block hash - a block hash
|
|
||||||
* @param {Function} callback - A function that accepts: Error and Array of hashes
|
|
||||||
*/
|
|
||||||
DB.prototype.getHashes = function getHashes(tipHash, callback) {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
$.checkArgument(utils.isHash(tipHash));
|
|
||||||
|
|
||||||
var hashes = [];
|
|
||||||
var depth = 0;
|
|
||||||
|
|
||||||
function getHashAndContinue(err, hash) {
|
|
||||||
/* jshint maxstatements: 20 */
|
|
||||||
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
depth++;
|
|
||||||
|
|
||||||
hashes.unshift(hash);
|
|
||||||
|
|
||||||
if (hash === self.genesis.hash) {
|
|
||||||
// Stop at the genesis block
|
|
||||||
self.cache.chainHashes[tipHash] = hashes;
|
|
||||||
|
|
||||||
callback(null, hashes);
|
|
||||||
} else if(self.cache.chainHashes[hash]) {
|
|
||||||
hashes.shift();
|
|
||||||
hashes = self.cache.chainHashes[hash].concat(hashes);
|
|
||||||
self.cache.chainHashes[tipHash] = hashes;
|
|
||||||
if(hash !== tipHash) {
|
|
||||||
delete self.cache.chainHashes[hash];
|
|
||||||
}
|
|
||||||
callback(null, hashes);
|
|
||||||
} else {
|
|
||||||
// Continue with the previous hash
|
|
||||||
// check cache first
|
|
||||||
var prevHash = self.cache.hashes[hash];
|
|
||||||
if(prevHash) {
|
|
||||||
// Don't let the stack get too deep. Otherwise we will crash.
|
|
||||||
if(depth >= MAX_STACK_DEPTH) {
|
|
||||||
depth = 0;
|
|
||||||
return setImmediate(function() {
|
|
||||||
getHashAndContinue(null, prevHash);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return getHashAndContinue(null, prevHash);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// do a db call if we don't have it
|
|
||||||
self.getPrevHash(hash, function(err, prevHash) {
|
|
||||||
if(err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
return getHashAndContinue(null, prevHash);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getHashAndContinue(null, tipHash);
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function will find the common ancestor between the current chain and a forked block,
|
* 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.
|
* by moving backwards from the forked block until it meets the current chain.
|
||||||
@ -486,41 +410,60 @@ DB.prototype.findCommonAncestor = function(block, done) {
|
|||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
// The current chain of hashes will likely already be available in a cache.
|
var mainPosition = self.tip.hash;
|
||||||
self.getHashes(self.tip.hash, function(err, currentHashes) {
|
var forkPosition = block.hash;
|
||||||
if (err) {
|
|
||||||
done(err);
|
var mainHashesMap = {};
|
||||||
|
var forkHashesMap = {};
|
||||||
|
|
||||||
|
mainHahesMap[mainPosition] = true;
|
||||||
|
forkHashesMap[forkPosition] = true;
|
||||||
|
|
||||||
|
var commonAncestor = null;
|
||||||
|
|
||||||
|
async.whilst(
|
||||||
|
function() {
|
||||||
|
return !commonAncestor;
|
||||||
|
},
|
||||||
|
function(next) {
|
||||||
|
if(mainPosition) {
|
||||||
|
var mainBlockIndex = self.node.services.bitcoind.getBlockIndex(mainTip);
|
||||||
|
if(mainBlockIndex && mainBlockIndex.prevHash) {
|
||||||
|
mainHashesMap[mainBlockIndex.prevHash] = true;
|
||||||
|
mainPosition = mainBlockIndex.prevHash;
|
||||||
|
} else {
|
||||||
|
mainPosition = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(forkPosition) {
|
||||||
|
var forkBlockIndex = self.node.services.bitcoind.getBlockIndex(forkTip);
|
||||||
|
if(forkBlockIndex && forkBlockIndex.prevHash) {
|
||||||
|
forkHashesMap[forkBlockIndex.prevHash] = true;
|
||||||
|
forkPosition = forkBlockIndex.prevHash;
|
||||||
|
} else {
|
||||||
|
forkPosition = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(forkPosition && mainHashesMap[forkPosition]) {
|
||||||
|
commonAncestor = forkPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mainPosition && forkHashesMap[mainPosition]) {
|
||||||
|
commonAncestor = mainPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!mainPosition && !forkPosition) {
|
||||||
|
return next(new Error('Unknown common ancestor'));
|
||||||
|
}
|
||||||
|
|
||||||
|
setImmediate(next);
|
||||||
|
},
|
||||||
|
function(err) {
|
||||||
|
done(err, commonAncestor);
|
||||||
}
|
}
|
||||||
|
);
|
||||||
// Create a hash map for faster lookups
|
|
||||||
var currentHashesMap = {};
|
|
||||||
var length = currentHashes.length;
|
|
||||||
for (var i = 0; i < length; i++) {
|
|
||||||
currentHashesMap[currentHashes[i]] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: expose prevHash as a string from bitcore
|
|
||||||
var ancestorHash = BufferUtil.reverse(block.header.prevHash).toString('hex');
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
|
|
||||||
while(ancestorHash && !currentHashesMap[ancestorHash]) {
|
|
||||||
var blockIndex = self.node.services.bitcoind.getBlockIndex(ancestorHash);
|
|
||||||
ancestorHash = blockIndex ? blockIndex.prevHash : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hash map is no-longer needed, quickly let
|
|
||||||
// scavenging garbage collection know to cleanup
|
|
||||||
currentHashesMap = null;
|
|
||||||
|
|
||||||
if (!ancestorHash) {
|
|
||||||
return done(new Error('Unknown common ancestor.'));
|
|
||||||
}
|
|
||||||
|
|
||||||
done(null, ancestorHash);
|
|
||||||
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -620,28 +563,18 @@ DB.prototype.sync = function() {
|
|||||||
// Populate height
|
// Populate height
|
||||||
block.__height = self.tip.__height + 1;
|
block.__height = self.tip.__height + 1;
|
||||||
|
|
||||||
// Update cache.hashes
|
// Create indexes
|
||||||
self.cache.hashes[block.hash] = prevHash;
|
self.connectBlock(block, function(err) {
|
||||||
|
|
||||||
// Update cache.chainHashes
|
|
||||||
self.getHashes(block.hash, function(err, hashes) {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
return done(err);
|
return done(err);
|
||||||
}
|
}
|
||||||
// Create indexes
|
self.tip = block;
|
||||||
self.connectBlock(block, function(err) {
|
log.debug('Saving metadata');
|
||||||
if (err) {
|
self.saveMetadata();
|
||||||
return done(err);
|
log.debug('Chain added block to main chain');
|
||||||
}
|
self.emit('addblock', block);
|
||||||
self.tip = block;
|
setImmediate(done);
|
||||||
log.debug('Saving metadata');
|
|
||||||
self.saveMetadata();
|
|
||||||
log.debug('Chain added block to main chain');
|
|
||||||
self.emit('addblock', block);
|
|
||||||
setImmediate(done);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// This block doesn't progress the current tip, so we'll attempt
|
// This block doesn't progress the current tip, so we'll attempt
|
||||||
// to rewind the chain to the common ancestor of the block and
|
// to rewind the chain to the common ancestor of the block and
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user