348 lines
8.7 KiB
JavaScript
348 lines
8.7 KiB
JavaScript
'use strict';
|
|
var flocore = require('flocore-lib');
|
|
var BufferUtil = flocore.util.buffer;
|
|
var async = require('async');
|
|
|
|
function Reorg(node, block) {
|
|
this.node = node;
|
|
this.block = block;
|
|
this.db = block.db;
|
|
}
|
|
|
|
Reorg.prototype.handleReorg = function(newBlockHash, callback) {
|
|
var self = this;
|
|
|
|
self.handleConcurrentReorg(function(err) {
|
|
if(err) {
|
|
return callback(err);
|
|
}
|
|
|
|
self.findCommonAncestorAndNewHashes(self.block.tip.hash, newBlockHash, function(err, commonAncestor, newHashes) {
|
|
if(err) {
|
|
return callback(err);
|
|
}
|
|
|
|
self.rewindBothTips(commonAncestor, function(err) {
|
|
if(err) {
|
|
return callback(err);
|
|
}
|
|
self.fastForwardBothTips(newHashes, callback);
|
|
});
|
|
});
|
|
});
|
|
};
|
|
|
|
Reorg.prototype.handleConcurrentReorg = function(callback) {
|
|
var self = this;
|
|
|
|
if(self.block.concurrentTip.hash === self.block.tip.hash) {
|
|
return callback();
|
|
}
|
|
|
|
self.findCommonAncestorAndNewHashes(
|
|
self.block.concurrentTip.hash,
|
|
self.block.tip.hash,
|
|
function(err, commonAncestor, newHashes) {
|
|
if(err) {
|
|
return callback(err);
|
|
}
|
|
|
|
self.rewindConcurrentTip(commonAncestor, function(err) {
|
|
if(err) {
|
|
return callback(err);
|
|
}
|
|
|
|
self.fastForwardConcurrentTip(newHashes, callback);
|
|
});
|
|
});
|
|
};
|
|
|
|
Reorg.prototype.rewindConcurrentTip = function(commonAncestor, callback) {
|
|
var self = this;
|
|
|
|
async.whilst(
|
|
function() {
|
|
return self.block.concurrentTip.hash !== commonAncestor;
|
|
},
|
|
function(next) {
|
|
self.block.getBlockOperations(self.block.concurrentTip, false, 'concurrent', function(err, operations) {
|
|
if(err) {
|
|
return next(err);
|
|
}
|
|
|
|
operations.push(self.block.getTipOperation(self.block.concurrentTip, false, 'concurrentTip'));
|
|
self.db.batch(operations, function(err) {
|
|
if(err) {
|
|
return next(err);
|
|
}
|
|
|
|
var prevHash = BufferUtil.reverse(self.block.concurrentTip.header.prevHash).toString('hex');
|
|
|
|
self.block.getBlocks([prevHash], function(err, blocks) {
|
|
if(err) {
|
|
return next(err);
|
|
}
|
|
|
|
self.block.concurrentTip = blocks[0];
|
|
next();
|
|
});
|
|
});
|
|
});
|
|
|
|
|
|
},
|
|
callback
|
|
);
|
|
};
|
|
|
|
Reorg.prototype.fastForwardConcurrentTip = function(newHashes, callback) {
|
|
var self = this;
|
|
|
|
async.eachSeries(newHashes, function(hash, next) {
|
|
self.block.getBlocks([hash], function(err, blocks) {
|
|
if(err) {
|
|
return next(err);
|
|
}
|
|
|
|
self.block.getBlockOperations(blocks[0], true, 'concurrent', function(err, operations) {
|
|
if(err) {
|
|
return next(err);
|
|
}
|
|
|
|
operations.push(self.block.getTipOperation(blocks[0], true, 'concurrentTip'));
|
|
self.db.batch(operations, function(err) {
|
|
if(err) {
|
|
return next(err);
|
|
}
|
|
|
|
self.block.concurrentTip = blocks[0];
|
|
next();
|
|
});
|
|
});
|
|
});
|
|
}, callback);
|
|
};
|
|
|
|
Reorg.prototype.rewindBothTips = function(commonAncestor, callback) {
|
|
var self = this;
|
|
|
|
async.whilst(
|
|
function() {
|
|
return self.block.tip.hash !== commonAncestor;
|
|
},
|
|
function(next) {
|
|
async.parallel(
|
|
[
|
|
function(next) {
|
|
self.block.getBlockOperations(self.block.concurrentTip, false, 'concurrent', function(err, operations) {
|
|
if(err) {
|
|
return next(err);
|
|
}
|
|
operations.push(self.block.getTipOperation(self.block.concurrentTip, false, 'concurrentTip'));
|
|
next(null, operations);
|
|
});
|
|
},
|
|
function(next) {
|
|
self.block.getBlockOperations(self.block.tip, false, 'serial', function(err, operations) {
|
|
if(err) {
|
|
return next(err);
|
|
}
|
|
|
|
operations.push(self.block.getTipOperation(self.block.tip, false));
|
|
next(null, operations);
|
|
});
|
|
}
|
|
],
|
|
function(err, results) {
|
|
if(err) {
|
|
return callback(err);
|
|
}
|
|
|
|
var operations = results[0].concat(results[1]);
|
|
self.db.batch(operations, function(err) {
|
|
if(err) {
|
|
return next(err);
|
|
}
|
|
|
|
var prevHash = BufferUtil.reverse(self.block.tip.header.prevHash).toString('hex');
|
|
|
|
self.block.getBlocks([prevHash], function(err, blocks) {
|
|
|
|
if(err) {
|
|
return next(err);
|
|
}
|
|
|
|
self.block.concurrentTip = blocks[0];
|
|
self.block.tip = blocks[0];
|
|
next();
|
|
});
|
|
});
|
|
}
|
|
);
|
|
},
|
|
callback
|
|
);
|
|
};
|
|
|
|
Reorg.prototype.fastForwardBothTips = function(newHashes, callback) {
|
|
var self = this;
|
|
|
|
async.eachSeries(newHashes, function(hash, next) {
|
|
self.block.getBlocks([hash], function(err, blocks) {
|
|
if(err) {
|
|
return next(err);
|
|
}
|
|
|
|
async.parallel(
|
|
[
|
|
function(next) {
|
|
self.block.getBlockOperations(blocks[0], true, 'concurrent', function(err, operations) {
|
|
if(err) {
|
|
return next(err);
|
|
}
|
|
|
|
operations.push(self.block.getTipOperation(blocks[0], true, 'concurrentTip'));
|
|
next(null, operations);
|
|
});
|
|
},
|
|
function(next) {
|
|
self.block.getBlockOperations(blocks[0], true, 'serial', function(err, operations) {
|
|
if(err) {
|
|
return next(err);
|
|
}
|
|
|
|
operations.push(self.block.getTipOperation(blocks[0], true));
|
|
next(null, operations);
|
|
});
|
|
}
|
|
],
|
|
function(err, results) {
|
|
if(err) {
|
|
return next(err);
|
|
}
|
|
|
|
var operations = results[0].concat(results[1]);
|
|
|
|
self.db.batch(operations, function(err) {
|
|
if(err) {
|
|
return next(err);
|
|
}
|
|
|
|
self.block.concurrentTip = blocks[0];
|
|
self.block.tip = blocks[0];
|
|
next();
|
|
});
|
|
}
|
|
);
|
|
});
|
|
}, callback);
|
|
};
|
|
|
|
Reorg.prototype.findCommonAncestorAndNewHashes = function(oldTipHash, newTipHash, callback) {
|
|
var self = this;
|
|
|
|
var mainPosition = oldTipHash;
|
|
var forkPosition = newTipHash;
|
|
|
|
var mainHashesMap = {};
|
|
var forkHashesMap = {};
|
|
|
|
mainHashesMap[mainPosition] = true;
|
|
forkHashesMap[forkPosition] = true;
|
|
|
|
var commonAncestor = null;
|
|
var newHashes = [forkPosition];
|
|
|
|
async.whilst(
|
|
function() {
|
|
return !commonAncestor;
|
|
},
|
|
function(next) {
|
|
async.parallel(
|
|
[
|
|
function(next) {
|
|
if(!mainPosition) {
|
|
return next();
|
|
}
|
|
|
|
self.block.getBlockHeader(mainPosition, function(err, mainBlockHeader) {
|
|
if(err) {
|
|
return next(err);
|
|
}
|
|
|
|
if(mainBlockHeader && mainBlockHeader.prevHash) {
|
|
mainHashesMap[mainBlockHeader.prevHash] = true;
|
|
mainPosition = mainBlockHeader.prevHash;
|
|
} else {
|
|
mainPosition = null;
|
|
}
|
|
next();
|
|
});
|
|
},
|
|
function(next) {
|
|
if(!forkPosition) {
|
|
return next();
|
|
}
|
|
|
|
self.block.getBlockHeader(forkPosition, function(err, forkBlockHeader) {
|
|
if(err) {
|
|
return next(err);
|
|
}
|
|
|
|
if(forkBlockHeader && forkBlockHeader.prevHash) {
|
|
forkHashesMap[forkBlockHeader.prevHash] = true;
|
|
forkPosition = forkBlockHeader.prevHash;
|
|
newHashes.unshift(forkPosition);
|
|
} else {
|
|
forkPosition = null;
|
|
}
|
|
|
|
next();
|
|
});
|
|
}
|
|
],
|
|
function(err) {
|
|
if(err) {
|
|
return next(err);
|
|
}
|
|
|
|
if(forkPosition && mainHashesMap[forkPosition]) {
|
|
commonAncestor = forkPosition;
|
|
}
|
|
|
|
if(mainPosition && forkHashesMap[mainPosition]) {
|
|
commonAncestor = mainPosition;
|
|
}
|
|
|
|
if(!mainPosition && !forkPosition) {
|
|
return next(new Error('Unknown common ancestor'));
|
|
}
|
|
|
|
next();
|
|
}
|
|
);
|
|
},
|
|
function(err) {
|
|
if(err) {
|
|
return callback(err);
|
|
}
|
|
|
|
// New hashes are those that are > common ancestor
|
|
var commonAncestorFound = false;
|
|
for(var i = newHashes.length - 1; i >= 0; i--) {
|
|
if(newHashes[i] === commonAncestor) {
|
|
commonAncestorFound = true;
|
|
}
|
|
|
|
if(commonAncestorFound) {
|
|
newHashes.shift();
|
|
}
|
|
}
|
|
|
|
callback(null, commonAncestor, newHashes);
|
|
}
|
|
);
|
|
};
|
|
|
|
module.exports = Reorg;
|