lisk-framework
Version:
Lisk blockchain application platform
181 lines • 8.42 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.FastChainSwitchingMechanism = void 0;
const base_synchronizer_1 = require("./base_synchronizer");
const utils_1 = require("./utils");
const errors_1 = require("./errors");
class FastChainSwitchingMechanism extends base_synchronizer_1.BaseSynchronizer {
constructor({ logger, chain, blockExecutor, network }) {
super(logger, chain, network);
this._chain = chain;
this.blockExecutor = blockExecutor;
}
async run(receivedBlock, peerId) {
const highestCommonBlock = await this._requestLastCommonBlock(peerId);
const blocks = await this._queryBlocks(receivedBlock, highestCommonBlock, peerId);
this._validateBlocks(blocks, peerId);
await this._switchChain(highestCommonBlock, blocks, peerId);
}
async isValidFor(receivedBlock, peerId) {
if (!peerId) {
return false;
}
const { lastBlock } = this._chain;
const validators = await this.blockExecutor.getCurrentValidators();
const twoRounds = validators.length * 2;
if (Math.abs(receivedBlock.header.height - lastBlock.header.height) > twoRounds) {
return false;
}
return (validators.find(v => v.address.equals(receivedBlock.header.generatorAddress) && v.bftWeight > BigInt(0)) !== undefined);
}
async _requestBlocksWithinIDs(peerId, fromId, toId) {
const maxFailedAttempts = 10;
const blocks = [];
let failedAttempts = 0;
let lastFetchedID = fromId;
while (failedAttempts < maxFailedAttempts) {
let chunkOfBlocks = [];
try {
chunkOfBlocks = await this._getBlocksFromNetwork(peerId, lastFetchedID);
}
catch (error) {
failedAttempts += 1;
continue;
}
chunkOfBlocks.sort((a, b) => a.header.height - b.header.height);
blocks.push(...chunkOfBlocks);
[
{
header: { id: lastFetchedID },
},
] = chunkOfBlocks.slice(-1);
const index = blocks.findIndex(block => block.header.id.equals(toId));
if (index > -1) {
return blocks.splice(0, index + 1);
}
}
return blocks;
}
async _queryBlocks(receivedBlock, highestCommonBlock, peerId) {
if (!highestCommonBlock) {
throw new errors_1.ApplyPenaltyAndAbortError(peerId, "Peer didn't return a common block");
}
if (highestCommonBlock.height < this.blockExecutor.getFinalizedHeight()) {
throw new errors_1.ApplyPenaltyAndAbortError(peerId, `Common block height ${highestCommonBlock.height} is lower than the finalized height of the chain ${this.blockExecutor.getFinalizedHeight()}`);
}
const validators = await this.blockExecutor.getCurrentValidators();
if (this._chain.lastBlock.header.height - highestCommonBlock.height > validators.length * 2 ||
receivedBlock.header.height - highestCommonBlock.height > validators.length * 2) {
throw new errors_1.AbortError(`Height difference between both chains is higher than ${validators.length * 2}`);
}
this._logger.debug({
peerId,
fromBlockId: highestCommonBlock.id,
toBlockId: receivedBlock.header.id,
}, 'Requesting blocks within ID range from peer');
const blocks = await this._requestBlocksWithinIDs(peerId, highestCommonBlock.id, receivedBlock.header.id);
if (!blocks.length) {
throw new errors_1.ApplyPenaltyAndAbortError(peerId, `Peer didn't return any requested block within IDs ${highestCommonBlock.id.toString('hex')} and ${receivedBlock.header.id.toString('hex')}`);
}
return blocks;
}
_validateBlocks(blocks, peerId) {
this._logger.debug({
heights: blocks.map(b => b.header.height).toString(),
}, 'Validating blocks');
try {
for (const block of blocks) {
this._logger.trace({
blockId: block.header.id.toString('hex'),
height: block.header.height,
}, 'Validating block');
this.blockExecutor.validate(block);
}
}
catch (err) {
throw new errors_1.ApplyPenaltyAndAbortError(peerId, 'Block validation failed');
}
this._logger.debug('Successfully validated blocks');
}
async _applyBlocks(blocksToApply) {
try {
for (const block of blocksToApply) {
if (this._stop) {
return;
}
this._logger.trace({
blockId: block.header.id.toString('hex'),
height: block.header.height,
}, 'Applying blocks');
await this.blockExecutor.verify(block);
await this.blockExecutor.executeValidated(block, { skipBroadcast: true });
}
}
catch (e) {
throw new errors_1.BlockProcessingError();
}
}
async _handleBlockProcessingFailure(error, highestCommonBlock, peerId) {
this._logger.error({ err: error }, 'Error while processing blocks');
this._logger.debug({ height: highestCommonBlock.height }, 'Deleting blocks after height');
await (0, utils_1.deleteBlocksAfterHeight)(this.blockExecutor, this._chain, this._logger, highestCommonBlock.height);
this._logger.debug('Restoring blocks from temporary table');
await (0, utils_1.restoreBlocks)(this._chain, this.blockExecutor);
throw new errors_1.ApplyPenaltyAndAbortError(peerId, 'Detected invalid block while processing list of requested blocks');
}
async _switchChain(highestCommonBlock, blocksToApply, peerId) {
this._logger.info('Switching chain');
this._logger.debug({ height: highestCommonBlock.height }, `Deleting blocks after height ${highestCommonBlock.height}`);
await (0, utils_1.deleteBlocksAfterHeight)(this.blockExecutor, this._chain, this._logger, highestCommonBlock.height, true);
try {
this._logger.debug({
blocks: blocksToApply.map(block => ({
blockId: block.header.id,
height: block.header.height,
})),
}, 'Applying blocks');
await this._applyBlocks(blocksToApply);
this._logger.info({
currentHeight: this._chain.lastBlock.header.height,
highestCommonBlockHeight: highestCommonBlock.height,
}, 'Successfully switched chains. Node is now up to date');
}
catch (err) {
if (err instanceof errors_1.BlockProcessingError) {
await this._handleBlockProcessingFailure(err, highestCommonBlock, peerId);
}
else {
throw err;
}
}
finally {
this._logger.debug('Cleaning blocks temp table');
await (0, utils_1.clearBlocksTempTable)(this._chain);
}
}
_computeLastTwoRoundsHeights(numberOfValidators) {
return new Array(Math.min(numberOfValidators * 2, this._chain.lastBlock.header.height + 1))
.fill(0)
.map((_, index) => this._chain.lastBlock.header.height - index);
}
async _requestLastCommonBlock(peerId) {
this._logger.debug({ peerId }, 'Requesting the last common block with peer');
const requestLimit = 10;
let numberOfRequests = 1;
const validators = await this.blockExecutor.getCurrentValidators();
const heightList = this._computeLastTwoRoundsHeights(validators.length);
while (numberOfRequests < requestLimit) {
const blockIds = (await this._chain.dataAccess.getBlockHeadersWithHeights(heightList)).map(block => block.id);
try {
const commonBlock = await this._getHighestCommonBlockFromNetwork(peerId, blockIds);
return commonBlock;
}
catch (error) {
numberOfRequests += 1;
}
}
return undefined;
}
}
exports.FastChainSwitchingMechanism = FastChainSwitchingMechanism;
//# sourceMappingURL=fast_chain_switching_mechanism.js.map