lisk-framework
Version:
Lisk blockchain application platform
185 lines • 9.06 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.BFTMethod = void 0;
const lisk_cryptography_1 = require("@liskhq/lisk-cryptography");
const lisk_utils_1 = require("@liskhq/lisk-utils");
const utils_1 = require("./utils");
const bft_params_1 = require("./bft_params");
const constants_1 = require("./constants");
const schemas_1 = require("./schemas");
const errors_1 = require("./errors");
class BFTMethod {
blockTime() {
return this._blockTime;
}
init(batchSize, blockTime) {
this._batchSize = batchSize;
this._blockTime = blockTime;
}
areHeadersContradicting(bftHeader1, bftHeader2) {
if (bftHeader1.id.equals(bftHeader2.id)) {
return false;
}
return (0, utils_1.areDistinctHeadersContradicting)(bftHeader1, bftHeader2);
}
async isHeaderContradictingChain(stateStore, header) {
const votesStore = stateStore.getStore(constants_1.MODULE_STORE_PREFIX_BFT, constants_1.STORE_PREFIX_BFT_VOTES);
const bftVotes = await votesStore.getWithSchema(constants_1.EMPTY_KEY, schemas_1.bftVotesSchema);
for (const bftBlock of bftVotes.blockBFTInfos) {
if (bftBlock.generatorAddress.equals(header.generatorAddress)) {
return (0, utils_1.areDistinctHeadersContradicting)(bftBlock, header);
}
}
return false;
}
async existBFTParameters(stateStore, height) {
const paramsStore = stateStore.getStore(constants_1.MODULE_STORE_PREFIX_BFT, constants_1.STORE_PREFIX_BFT_PARAMETERS);
return paramsStore.has(lisk_cryptography_1.utils.intToBuffer(height, 4));
}
async getBFTParameters(stateStore, height) {
const paramsStore = stateStore.getStore(constants_1.MODULE_STORE_PREFIX_BFT, constants_1.STORE_PREFIX_BFT_PARAMETERS);
return (0, bft_params_1.getBFTParameters)(paramsStore, height);
}
async getBFTParametersActiveValidators(stateStore, height) {
const bftParams = await this.getBFTParameters(stateStore, height);
return {
...bftParams,
validators: bftParams.validators.filter(v => v.bftWeight > BigInt(0)),
};
}
async getBFTHeights(stateStore) {
const votesStore = stateStore.getStore(constants_1.MODULE_STORE_PREFIX_BFT, constants_1.STORE_PREFIX_BFT_VOTES);
const bftVotes = await votesStore.getWithSchema(constants_1.EMPTY_KEY, schemas_1.bftVotesSchema);
return {
maxHeightPrevoted: bftVotes.maxHeightPrevoted,
maxHeightPrecommitted: bftVotes.maxHeightPrecommitted,
maxHeightCertified: bftVotes.maxHeightCertified,
};
}
async impliesMaximalPrevotes(stateStore, header) {
const votesStore = stateStore.getStore(constants_1.MODULE_STORE_PREFIX_BFT, constants_1.STORE_PREFIX_BFT_VOTES);
const bftVotes = await votesStore.getWithSchema(constants_1.EMPTY_KEY, schemas_1.bftVotesSchema);
if (bftVotes.blockBFTInfos.length === 0) {
return header.height > header.maxHeightGenerated;
}
const [currentTip] = bftVotes.blockBFTInfos;
if (currentTip.height + 1 !== header.height) {
throw new Error(`Input header with height ${header.height} is invalid. It must be ${currentTip.height + 1}.`);
}
const previousHeight = header.maxHeightGenerated;
if (previousHeight >= header.height) {
return false;
}
const offset = currentTip.height - previousHeight;
if (offset >= bftVotes.blockBFTInfos.length) {
return true;
}
if (!bftVotes.blockBFTInfos[offset].generatorAddress.equals(header.generatorAddress)) {
return false;
}
return true;
}
async getNextHeightBFTParameters(stateStore, height) {
const paramsStore = stateStore.getStore(constants_1.MODULE_STORE_PREFIX_BFT, constants_1.STORE_PREFIX_BFT_PARAMETERS);
const start = lisk_cryptography_1.utils.intToBuffer(height + 1, 4);
const end = lisk_cryptography_1.utils.intToBuffer(constants_1.MAX_UINT32, 4);
const results = await paramsStore.iterate({
limit: 1,
gte: start,
lte: end,
});
if (results.length !== 1) {
throw new errors_1.BFTParameterNotFoundError();
}
const [result] = results;
return result.key.readUInt32BE(0);
}
async setBFTParameters(stateStore, precommitThreshold, certificateThreshold, validators) {
if (validators.length > this._batchSize) {
throw new Error(`Invalid validators size. The number of validators can be at most the batch size ${this._batchSize}.`);
}
const validatorAddresses = [];
const validatorValidBLSKeys = [];
for (const validator of validators) {
validatorAddresses.push(validator.address);
if (!validator.blsKey.equals(constants_1.EMPTY_BLS_KEY)) {
validatorValidBLSKeys.push(validator.blsKey);
}
}
if (!lisk_utils_1.objects.bufferArrayUniqueItems(validatorAddresses)) {
throw new Error('Provided validator addresses are not unique.');
}
if (!lisk_utils_1.objects.bufferArrayUniqueItems(validatorValidBLSKeys)) {
throw new Error('Provided validator BLS keys are not unique.');
}
let aggregateBFTWeight = BigInt(0);
for (const validator of validators) {
if (validator.bftWeight < 0) {
throw new Error('BFT Weight must be 0 or greater.');
}
aggregateBFTWeight += validator.bftWeight;
}
if (aggregateBFTWeight / BigInt(3) + BigInt(1) > precommitThreshold ||
precommitThreshold > aggregateBFTWeight) {
throw new Error('Invalid precommitThreshold input.');
}
if (aggregateBFTWeight / BigInt(3) + BigInt(1) > certificateThreshold ||
certificateThreshold > aggregateBFTWeight) {
throw new Error('Invalid certificateThreshold input.');
}
const validatorsWithBFTWeight = validators
.filter(validator => validator.bftWeight > BigInt(0))
.map(validator => ({ bftWeight: validator.bftWeight, blsKey: validator.blsKey }));
(0, utils_1.sortValidatorsByBLSKey)(validatorsWithBFTWeight);
const validatorsHash = (0, utils_1.computeValidatorsHash)(validatorsWithBFTWeight, certificateThreshold);
const bftParams = {
prevoteThreshold: (BigInt(2) * aggregateBFTWeight) / BigInt(3) + BigInt(1),
precommitThreshold,
certificateThreshold,
validators,
validatorsHash,
};
const votesStore = stateStore.getStore(constants_1.MODULE_STORE_PREFIX_BFT, constants_1.STORE_PREFIX_BFT_VOTES);
const bftVotes = await votesStore.getWithSchema(constants_1.EMPTY_KEY, schemas_1.bftVotesSchema);
const nextHeight = (bftVotes.blockBFTInfos.length > 0
? bftVotes.blockBFTInfos[0].height
: bftVotes.maxHeightPrevoted) + 1;
const paramsStore = stateStore.getStore(constants_1.MODULE_STORE_PREFIX_BFT, constants_1.STORE_PREFIX_BFT_PARAMETERS);
const nextHeightBytes = lisk_cryptography_1.utils.intToBuffer(nextHeight, 4);
await paramsStore.setWithSchema(nextHeightBytes, bftParams, schemas_1.bftParametersSchema);
const nextActiveValidators = [];
for (const validator of validators) {
const existingValidator = bftVotes.activeValidatorsVoteInfo.find(v => v.address.equals(validator.address));
if (existingValidator) {
nextActiveValidators.push(existingValidator);
continue;
}
nextActiveValidators.push({
address: validator.address,
minActiveHeight: nextHeight,
largestHeightPrecommit: nextHeight - 1,
});
}
(0, utils_1.sortValidatorsByAddress)(nextActiveValidators);
bftVotes.activeValidatorsVoteInfo = nextActiveValidators;
await votesStore.setWithSchema(constants_1.EMPTY_KEY, bftVotes, schemas_1.bftVotesSchema);
}
async getGeneratorAtTimestamp(stateStore, height, timestamp) {
const paramsStore = stateStore.getStore(constants_1.MODULE_STORE_PREFIX_BFT, constants_1.STORE_PREFIX_BFT_PARAMETERS);
const bftParams = await (0, bft_params_1.getBFTParameters)(paramsStore, height);
const currentSlot = this.getSlotNumber(timestamp);
const generator = bftParams.validators[currentSlot % bftParams.validators.length];
return generator;
}
getSlotNumber(timestamp) {
return Math.floor(timestamp / this._blockTime);
}
getSlotTime(slot) {
return slot * this._blockTime;
}
isWithinTimeslot(slot, timestamp) {
return this.getSlotNumber(timestamp) === slot;
}
}
exports.BFTMethod = BFTMethod;
//# sourceMappingURL=method.js.map
;