mokka
Version:
Mokka Consensus Algorithm implementation in Javascript
157 lines • 8.6 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.VoteApi = void 0;
const MessageTypes_1 = __importDefault(require("../constants/MessageTypes"));
const NodeStates_1 = __importDefault(require("../constants/NodeStates"));
const VoteModel_1 = require("../models/VoteModel");
const utils = __importStar(require("../utils/cryptoUtils"));
const MessageApi_1 = require("./MessageApi");
const EventTypes_1 = __importDefault(require("../constants/EventTypes"));
class VoteApi {
constructor(mokka) {
this.mokka = mokka;
this.messageApi = new MessageApi_1.MessageApi(mokka);
}
async vote(packet) {
if (!packet.data.nonce ||
packet.data.nonce > Date.now() ||
Date.now() - packet.data.nonce > this.mokka.electionTimeout) {
this.mokka.logger.trace(`[vote] peer ${packet.publicKey} hasn't provided a correct nonce`);
return this.messageApi.packet(MessageTypes_1.default.VOTED);
}
if (this.mokka.term >= packet.term ||
(this.mokka.vote && this.mokka.vote.term >= packet.term) ||
!this.mokka.checkTermNumber(packet.term) ||
!this.mokka.checkPublicKeyCanBeLeaderNextRound(packet.publicKey)) {
return this.messageApi.packet(MessageTypes_1.default.VOTED);
}
const wasAckFromLeader = await this.waitForNextAck();
if (wasAckFromLeader) {
this.mokka.logger.trace(`[vote] peer ${packet.publicKey} asked to vote with alive leader`);
return this.messageApi.packet(MessageTypes_1.default.VOTED);
}
const isCustomRulePassed = await this.mokka.customVoteRule(packet);
if (!isCustomRulePassed) {
return this.messageApi.packet(MessageTypes_1.default.VOTED);
}
const publicKeysRootForTerm = utils.buildPublicKeysRootForTerm(this.mokka.publicKeysRoot, packet.term, packet.data.nonce, packet.publicKey);
const vote = new VoteModel_1.VoteModel(packet.data.nonce, packet.term, publicKeysRootForTerm);
this.mokka.setVote(vote);
const startBuildVote = Date.now();
const signature = utils.buildPartialSignature(this.mokka.privateKey, packet.term, packet.data.nonce, publicKeysRootForTerm);
this.mokka.logger.trace(`built vote in ${Date.now() - startBuildVote}`);
return this.messageApi.packet(MessageTypes_1.default.VOTED, {
signature
});
}
async voted(packet) {
if (NodeStates_1.default.CANDIDATE !== this.mokka.state || !packet.data) {
return null;
}
const startPartialSigVerificationTime = Date.now();
const isValidPartialSignature = utils.partialSignatureVerify(packet.data.signature, packet.publicKey, this.mokka.vote.nonce, this.mokka.term, this.mokka.vote.publicKeysRootForTerm);
this.mokka.logger.trace(`verified partial signature in ${Date.now() - startPartialSigVerificationTime}`);
if (!isValidPartialSignature) {
this.mokka.logger.trace(`[voted] peer ${packet.publicKey} provided bad signature`);
return null;
}
if (!this.mokka.vote.repliesPublicKeyToSignatureMap.has(packet.publicKey)) {
this.mokka.vote.repliesPublicKeyToSignatureMap.set(packet.publicKey, packet.data.signature);
}
const isQuorumReached = this.mokka.quorum(this.mokka.vote.repliesPublicKeyToSignatureMap.size);
if (!isQuorumReached)
return null;
const fullSigBuildTime = Date.now();
const fullSignature = utils.buildSharedSignature(Array.from(this.mokka.vote.repliesPublicKeyToSignatureMap.values()));
this.mokka.logger.trace(`full signature has been built in ${Date.now() - fullSigBuildTime}`);
const participantPublicKeys = Array.from(this.mokka.vote.repliesPublicKeyToSignatureMap.keys()).sort();
const sharedPublicKeyXs = Array.from(this.mokka.vote.publicKeyToCombinationMap.keys());
const sharedPublicKeyX = sharedPublicKeyXs.find((sharedPublicKey) => this.mokka.vote.publicKeyToCombinationMap.get(sharedPublicKey).join('') === participantPublicKeys.join(''));
const fullSigVerificationTime = Date.now();
const isValid = utils.verify(fullSignature, sharedPublicKeyX);
this.mokka.logger.trace(`full signature has been verified in ${Date.now() - fullSigVerificationTime}`);
if (!isValid) {
this.mokka.logger.trace('invalid full signature');
this.mokka.setState(NodeStates_1.default.FOLLOWER, this.mokka.term, null);
return;
}
const compacted = `${this.mokka.vote.nonce}:${sharedPublicKeyX}:${fullSignature}`;
this.mokka.setState(NodeStates_1.default.LEADER, this.mokka.term, this.mokka.publicKey, compacted, this.mokka.vote.nonce);
return null;
}
async validateAndApplyLeader(packet) {
if (packet.term < this.mokka.term || !packet.proof) {
this.mokka.logger.trace('no proof supplied or term is outdated');
return null;
}
if (this.mokka.proof &&
this.mokka.proof === packet.proof &&
this.mokka.getProofMintedTime() + this.mokka.proofExpiration < Date.now()) {
this.mokka.logger.trace('proof expired');
return null;
}
if (this.mokka.proof !== packet.proof) {
const startProofValidation = Date.now();
const [proofNonce, proofSharedPublicKeyX, proofSignature] = packet.proof.split(':');
const publicKeysRootForTerm = utils.buildPublicKeysRootForTerm(this.mokka.publicKeysRoot, packet.term, proofNonce, packet.publicKey);
const proofSharedPublicKeyCombination = this.mokka.publicKeysCombinationsInQuorum.find((combination) => {
return utils.buildSharedPublicKeyX(combination, packet.term, proofNonce, publicKeysRootForTerm) === proofSharedPublicKeyX;
});
if (!proofSharedPublicKeyCombination) {
this.mokka.logger.trace(`proof contains unknown public key`);
return null;
}
if (!proofSharedPublicKeyCombination.includes(packet.publicKey)) {
this.mokka.logger.trace(`proof is used by wrong node`);
return null;
}
const isValid = utils.verify(proofSignature, proofSharedPublicKeyX);
if (!isValid) {
this.mokka.logger.trace(`wrong proof supplied`);
return null;
}
this.mokka.setState(NodeStates_1.default.FOLLOWER, packet.term, packet.publicKey, packet.proof, parseInt(proofNonce, 10));
this.mokka.logger.trace(`proof validated in ${Date.now() - startProofValidation}`);
return packet;
}
return packet;
}
async waitForNextAck() {
return await new Promise((res) => {
const timeoutHandler = () => {
this.mokka.removeListener(EventTypes_1.default.ACK, emitHandler);
res(false);
};
const timeoutId = setTimeout(timeoutHandler, this.mokka.heartbeatCtrl.safeHeartbeat());
const emitHandler = () => {
clearTimeout(timeoutId);
res(true);
};
this.mokka.once(EventTypes_1.default.ACK, emitHandler);
});
}
}
exports.VoteApi = VoteApi;
//# sourceMappingURL=VoteApi.js.map