lisk-framework
Version:
Lisk blockchain application platform
316 lines • 16.2 kB
JavaScript
"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