UNPKG

mokka

Version:

Mokka Consensus Algorithm implementation in Javascript

157 lines 8.6 kB
"use strict"; 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