UNPKG

lisk-framework

Version:

Lisk blockchain application platform

316 lines 16.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CommitPool = void 0; const lisk_chain_1 = require("@liskhq/lisk-chain"); const lisk_utils_1 = require("@liskhq/lisk-utils"); const lisk_cryptography_1 = require("@liskhq/lisk-cryptography"); const lisk_codec_1 = require("@liskhq/lisk-codec"); const constants_1 = require("./constants"); const errors_1 = require("../../bft/errors"); const utils_1 = require("./utils"); const schema_1 = require("./schema"); const commit_list_1 = require("./commit_list"); const metrics_1 = require("../../metrics/metrics"); class CommitPool { constructor(config) { this._metrics = { singleCommits: metrics_1.defaultMetrics.gauge('commitPool_numSingleCommits'), nonGossippedCommits: metrics_1.defaultMetrics.gauge('commitPool_numNonGossippedCommits'), nonGossippedCommitsLocal: metrics_1.defaultMetrics.gauge('commitPool_numNonGossippedCommitsLocal'), gossippedCommits: metrics_1.defaultMetrics.gauge('commitPool_numGossippedCommits'), job: metrics_1.defaultMetrics.histogram('commitPool_job', [0.01, 0.05, 0.1, 0.2, 0.5, 1, 5]), }; this._blockTime = config.blockTime; this._bftMethod = config.bftMethod; this._minCertifyHeight = config.minCertifyHeight; this._chain = config.chain; this._network = config.network; this._db = config.db; this._nonGossipedCommits = new commit_list_1.CommitList(); this._nonGossipedCommitsLocal = new commit_list_1.CommitList(); this._gossipedCommits = new commit_list_1.CommitList(); } start() { this._jobIntervalID = setInterval(async () => { const stateStore = new lisk_chain_1.StateStore(this._db); const endTimer = this._metrics.job.startTimer(); await this._job(stateStore); endTimer(); }, (this._blockTime / 2) * 1000); } stop() { clearInterval(this._jobIntervalID); } addCommit(commit, local = false) { if (!this._nonGossipedCommits.exists(commit) && !this._nonGossipedCommitsLocal.exists(commit)) { if (local) { this._nonGossipedCommitsLocal.add(commit); } else { this._nonGossipedCommits.add(commit); } this._metrics.singleCommits.inc(); } } async validateCommit(methodContext, commit) { const existsInNonGossiped = this._nonGossipedCommits.exists(commit); const existsInNonGossipedLocal = this._nonGossipedCommitsLocal.exists(commit); const existsInGossiped = this._gossipedCommits.exists(commit); const doesCommitExist = existsInGossiped || existsInNonGossiped || existsInNonGossipedLocal; if (doesCommitExist) { return false; } const maxRemovalHeight = await this.getMaxRemovalHeight(); if (commit.height <= maxRemovalHeight) { return false; } const currentHeight = this._chain.lastBlock.header.height; const { maxHeightPrecommitted } = await this._bftMethod.getBFTHeights(methodContext); const isCommitInRange = commit.height >= maxHeightPrecommitted - constants_1.COMMIT_RANGE_STORED && commit.height <= currentHeight; const doesBFTParamExistForNextHeight = await this._bftMethod.existBFTParameters(methodContext, commit.height + 1); if (!isCommitInRange && !doesBFTParamExistForNextHeight) { return false; } const blockHeaderAtCommitHeight = await this._chain.dataAccess.getBlockHeaderByHeight(commit.height); if (!blockHeaderAtCommitHeight.id.equals(commit.blockID)) { return false; } const { validators } = await this._bftMethod.getBFTParametersActiveValidators(methodContext, commit.height); const validator = validators.find(v => v.address.equals(commit.validatorAddress)); if (!validator) { throw new Error('Commit validator was not active for its height.'); } const unsignedCertificate = (0, utils_1.computeUnsignedCertificateFromBlockHeader)(blockHeaderAtCommitHeight); const { chainID } = this._chain; const isSingleCertificateVerified = (0, utils_1.verifySingleCertificateSignature)(validator.blsKey, commit.certificateSignature, chainID, unsignedCertificate); if (!isSingleCertificateVerified) { throw new Error('Certificate signature is not valid.'); } return true; } getCommitsByHeight(height) { const nonGossipedCommits = this._nonGossipedCommits.getByHeight(height); const nonGossipedCommitsLocal = this._nonGossipedCommitsLocal.getByHeight(height); const gossipedCommits = this._gossipedCommits.getByHeight(height); return [...nonGossipedCommits, ...nonGossipedCommitsLocal, ...gossipedCommits]; } createSingleCommit(blockHeader, validatorInfo, chainID) { return { blockID: blockHeader.id, height: blockHeader.height, validatorAddress: validatorInfo.address, certificateSignature: (0, utils_1.signCertificate)(validatorInfo.blsSecretKey, chainID, (0, utils_1.computeUnsignedCertificateFromBlockHeader)(blockHeader)), }; } async verifyAggregateCommit(stateStore, aggregateCommit) { const { maxHeightCertified, maxHeightPrecommitted } = await this._bftMethod.getBFTHeights(stateStore); if (aggregateCommit.aggregationBits.equals(constants_1.EMPTY_BUFFER) && aggregateCommit.certificateSignature.equals(constants_1.EMPTY_BUFFER) && aggregateCommit.height === maxHeightCertified) { return true; } if (aggregateCommit.aggregationBits.equals(constants_1.EMPTY_BUFFER) || aggregateCommit.certificateSignature.equals(constants_1.EMPTY_BUFFER)) { return false; } if (aggregateCommit.height <= maxHeightCertified) { return false; } if (aggregateCommit.height > maxHeightPrecommitted) { return false; } if (aggregateCommit.height < this._minCertifyHeight) { return false; } try { let heightNextBFTParameters = await this._bftMethod.getNextHeightBFTParameters(stateStore, maxHeightCertified + 1); heightNextBFTParameters = Math.max(heightNextBFTParameters, this._minCertifyHeight + 1); if (aggregateCommit.height > heightNextBFTParameters - 1) { return false; } } catch (err) { if (!(err instanceof errors_1.BFTParameterNotFoundError)) { throw err; } } const blockHeader = await this._chain.dataAccess.getBlockHeaderByHeight(aggregateCommit.height); const certificate = { ...(0, utils_1.computeUnsignedCertificateFromBlockHeader)(blockHeader), aggregationBits: aggregateCommit.aggregationBits, signature: aggregateCommit.certificateSignature, }; const { validators: activeValidators, certificateThreshold } = await this._bftMethod.getBFTParametersActiveValidators(stateStore, aggregateCommit.height); return (0, utils_1.verifyAggregateCertificateSignature)(activeValidators, certificateThreshold, this._chain.chainID, certificate); } async getAggregateCommit(methodContext) { return this._selectAggregateCommit(methodContext); } async aggregateSingleCommits(methodContext, singleCommits) { if (singleCommits.length === 0) { throw new Error('No single commit found'); } const { height } = singleCommits[0]; const { validators } = await this._bftMethod.getBFTParametersActiveValidators(methodContext, height); const addressToBlsKey = new lisk_utils_1.dataStructures.BufferMap(); const validatorKeys = []; for (const validator of validators) { addressToBlsKey.set(validator.address, validator.blsKey); validatorKeys.push(validator.blsKey); } const pubKeySignaturePairs = []; for (const commit of singleCommits) { const publicKey = addressToBlsKey.get(commit.validatorAddress); if (!publicKey) { throw new Error(`No bls public key entry found for validatorAddress ${commit.validatorAddress.toString('hex')}`); } pubKeySignaturePairs.push({ publicKey, signature: commit.certificateSignature }); } validatorKeys.sort((blsKeyA, blsKeyB) => blsKeyA.compare(blsKeyB)); const { aggregationBits, signature: aggregateSignature } = lisk_cryptography_1.bls.createAggSig(validatorKeys, pubKeySignaturePairs); return { height, aggregationBits, certificateSignature: aggregateSignature, }; } async getMaxRemovalHeight() { const blockHeader = await this._chain.dataAccess.getBlockHeaderByHeight(this._chain.finalizedHeight); return Math.max(blockHeader.aggregateCommit.height, this._minCertifyHeight - 1); } async _selectAggregateCommit(methodContext) { const { maxHeightCertified, maxHeightPrecommitted } = await this._bftMethod.getBFTHeights(methodContext); let heightNextBFTParameters; let nextHeight; try { heightNextBFTParameters = await this._bftMethod.getNextHeightBFTParameters(methodContext, maxHeightCertified + 1); heightNextBFTParameters = Math.max(heightNextBFTParameters, this._minCertifyHeight + 1); nextHeight = Math.min(heightNextBFTParameters - 1, maxHeightPrecommitted); } catch (err) { if (!(err instanceof errors_1.BFTParameterNotFoundError)) { throw err; } nextHeight = maxHeightPrecommitted; } const certifyUptoHeight = Math.max(maxHeightCertified, this._minCertifyHeight - 1); while (nextHeight > certifyUptoHeight) { const singleCommits = [ ...this._nonGossipedCommits.getByHeight(nextHeight), ...this._nonGossipedCommitsLocal.getByHeight(nextHeight), ...this._gossipedCommits.getByHeight(nextHeight), ]; let aggregateBFTWeight = BigInt(0); const { validators: bftParamValidators, certificateThreshold } = await this._bftMethod.getBFTParametersActiveValidators(methodContext, nextHeight); const nextValidators = singleCommits.map(commit => commit.validatorAddress); for (const matchingAddress of nextValidators) { const bftParamsValidatorInfo = bftParamValidators.find(bftParamValidator => bftParamValidator.address.equals(matchingAddress)); if (!bftParamsValidatorInfo) { throw new Error('Validator address not found in commit pool'); } aggregateBFTWeight += bftParamsValidatorInfo.bftWeight; } if (aggregateBFTWeight >= certificateThreshold) { return this.aggregateSingleCommits(methodContext, singleCommits); } nextHeight -= 1; } return { height: maxHeightCertified, aggregationBits: constants_1.EMPTY_BUFFER, certificateSignature: constants_1.EMPTY_BUFFER, }; } async _job(methodContext) { const removalHeight = await this.getMaxRemovalHeight(); const currentHeight = this._chain.lastBlock.header.height; const { maxHeightPrecommitted } = await this._bftMethod.getBFTHeights(methodContext); const deletedNonGossipedHeights = await this._getDeleteHeights(methodContext, this._nonGossipedCommits, removalHeight, maxHeightPrecommitted, currentHeight); for (const height of deletedNonGossipedHeights) { this._nonGossipedCommits.deleteByHeight(height); } this._metrics.nonGossippedCommits.set(this._nonGossipedCommits.size()); const deletedNonGossipedHeightsLocal = await this._getDeleteHeights(methodContext, this._nonGossipedCommitsLocal, removalHeight, maxHeightPrecommitted, currentHeight); for (const height of deletedNonGossipedHeightsLocal) { this._nonGossipedCommitsLocal.deleteByHeight(height); } this._metrics.nonGossippedCommitsLocal.set(this._nonGossipedCommitsLocal.size()); const deletedGossipedHeights = await this._getDeleteHeights(methodContext, this._gossipedCommits, removalHeight, maxHeightPrecommitted, currentHeight); for (const height of deletedGossipedHeights) { this._gossipedCommits.deleteByHeight(height); } this._metrics.gossippedCommits.set(this._gossipedCommits.size()); const nextHeight = this._chain.lastBlock.header.height + 1; const { validators } = await this._bftMethod.getBFTParametersActiveValidators(methodContext, nextHeight); const maxSelectedCommitsLength = 2 * validators.length; const allCommits = this._getAllCommits(); this._metrics.singleCommits.set(allCommits.length); const selectedCommits = []; for (const commit of allCommits) { if (selectedCommits.length >= maxSelectedCommitsLength) { break; } if (commit.height < maxHeightPrecommitted - constants_1.COMMIT_RANGE_STORED) { selectedCommits.push(commit); } } const sortedNonGossipedCommitsLocal = this._nonGossipedCommitsLocal.getAll(commit_list_1.COMMIT_SORT.DSC); for (const commit of sortedNonGossipedCommitsLocal) { if (selectedCommits.length >= maxSelectedCommitsLength) { break; } selectedCommits.push(commit); } const sortedNonGossipedCommits = this._nonGossipedCommits.getAll(commit_list_1.COMMIT_SORT.DSC); for (const commit of sortedNonGossipedCommits) { if (selectedCommits.length >= maxSelectedCommitsLength) { break; } selectedCommits.push(commit); } const encodedCommitArray = selectedCommits.map(commit => lisk_codec_1.codec.encode(schema_1.singleCommitSchema, commit)); this._network.send({ event: constants_1.NETWORK_EVENT_COMMIT_MESSAGES, data: lisk_codec_1.codec.encode(schema_1.singleCommitsNetworkPacketSchema, { commits: encodedCommitArray }), }); for (const commit of selectedCommits) { if (!this._gossipedCommits.exists(commit)) { this._gossipedCommits.add(commit); } this._nonGossipedCommits.deleteSingle(commit); this._nonGossipedCommitsLocal.deleteSingle(commit); } } async _getDeleteHeights(methodContext, commitMap, removalHeight, maxHeightPrecommitted, currentHeight) { const deleteHeights = []; for (const height of commitMap.getHeights()) { if (height <= removalHeight) { deleteHeights.push(height); continue; } const nonGossipedCommits = commitMap.getByHeight(height); for (const singleCommit of nonGossipedCommits) { if (maxHeightPrecommitted - constants_1.COMMIT_RANGE_STORED <= singleCommit.height && singleCommit.height <= currentHeight) { continue; } const changeOfBFTParams = await this._bftMethod.existBFTParameters(methodContext, singleCommit.height + 1); if (changeOfBFTParams) { continue; } deleteHeights.push(height); } } return deleteHeights; } _getAllCommits() { return [ ...this._nonGossipedCommits.getAll(), ...this._nonGossipedCommitsLocal.getAll(), ...this._gossipedCommits.getAll(), ].sort((a, b) => a.height - b.height); } } exports.CommitPool = CommitPool; //# sourceMappingURL=commit_pool.js.map