UNPKG

@clduab11/gemini-flow

Version:

Revolutionary AI agent swarm coordination platform with Google Services integration, multimedia processing, and production-ready monitoring. Features 8 Google AI services, quantum computing capabilities, and enterprise-grade security.

777 lines (671 loc) 19.5 kB
/** * Raft Consensus Protocol Implementation * * Implements the Raft consensus algorithm for distributed systems: * - Leader election * - Log replication * - Safety guarantees * - Membership changes * * Raft requires a majority quorum: Math.floor(n/2) + 1 */ import { EventEmitter } from "events"; import { createHash } from "crypto"; export interface RaftNode { id: string; address: string; state: "follower" | "candidate" | "leader"; currentTerm: number; votedFor: string | null; lastHeartbeat: Date; isActive: boolean; } export interface LogEntry { index: number; term: number; command: any; timestamp: Date; committed: boolean; } export interface RaftMessage { type: "request-vote" | "vote-response" | "append-entries" | "append-response"; term: number; senderId: string; targetId?: string; // RequestVote fields candidateId?: string; lastLogIndex?: number; lastLogTerm?: number; // AppendEntries fields leaderId?: string; prevLogIndex?: number; prevLogTerm?: number; entries?: LogEntry[]; leaderCommit?: number; // Response fields success?: boolean; voteGranted?: boolean; matchIndex?: number; timestamp: Date; } export interface RaftState { currentTerm: number; votedFor: string | null; log: LogEntry[]; commitIndex: number; lastApplied: number; // Leader state nextIndex: Map<string, number>; matchIndex: Map<string, number>; // Election state votesReceived: Set<string>; electionTimeout: number; heartbeatInterval: number; } export class RaftConsensus extends EventEmitter { private nodeId: string; private nodes: Map<string, RaftNode> = new Map(); private state: RaftState; private currentState: "follower" | "candidate" | "leader" = "follower"; // Timers private electionTimer: ReturnType<typeof setTimeout> | null = null; private heartbeatTimer: ReturnType<typeof setTimeout> | null = null; // Configuration private readonly minQuorum: number; // Raft: Math.floor(n/2) + 1 private readonly electionTimeoutMin: number = 150; // ms private readonly electionTimeoutMax: number = 300; // ms private readonly heartbeatInterval: number = 50; // ms // Performance tracking private performance: { electionsHeld: number; termChanges: number; logEntriesApplied: number; averageElectionTime: number; leadershipDuration: number; }; constructor(nodeId: string, totalNodes: number = 3) { super(); if (totalNodes < 1) { throw new Error("Raft requires at least 1 node"); } this.nodeId = nodeId; this.minQuorum = Math.floor(totalNodes / 2) + 1; this.state = { currentTerm: 0, votedFor: null, log: [], commitIndex: 0, lastApplied: 0, nextIndex: new Map(), matchIndex: new Map(), votesReceived: new Set(), electionTimeout: this.randomElectionTimeout(), heartbeatInterval: this.heartbeatInterval, }; this.performance = { electionsHeld: 0, termChanges: 0, logEntriesApplied: 0, averageElectionTime: 0, leadershipDuration: 0, }; this.startElectionTimeout(); } /** * Add a node to the Raft cluster */ public addNode(node: RaftNode): void { this.nodes.set(node.id, node); if (this.currentState === "leader") { // Initialize leader state for new node this.state.nextIndex.set(node.id, this.state.log.length + 1); this.state.matchIndex.set(node.id, 0); } this.emit("node-added", node); } /** * Remove a node from the Raft cluster */ public removeNode(nodeId: string): void { this.nodes.delete(nodeId); this.state.nextIndex.delete(nodeId); this.state.matchIndex.delete(nodeId); this.state.votesReceived.delete(nodeId); this.emit("node-removed", nodeId); } /** * Append a command to the log (leader only) */ public async appendCommand(command: any): Promise<boolean> { if (this.currentState !== "leader") { throw new Error("Only leader can append commands"); } if (!this.hasQuorum()) { throw new Error("Insufficient nodes for quorum"); } const logEntry: LogEntry = { index: this.state.log.length + 1, term: this.state.currentTerm, command: command, timestamp: new Date(), committed: false, }; this.state.log.push(logEntry); // Replicate to followers const success = await this.replicateEntry(logEntry); if (success) { logEntry.committed = true; this.state.commitIndex = logEntry.index; this.applyLogEntries(); this.emit("command-committed", { command, index: logEntry.index }); } return success; } /** * Process incoming Raft message */ public async processMessage(message: RaftMessage): Promise<void> { // Update term if message has higher term if (message.term > this.state.currentTerm) { this.state.currentTerm = message.term; this.state.votedFor = null; this.becomeFollower(); this.performance.termChanges++; } switch (message.type) { case "request-vote": await this.handleRequestVote(message); break; case "vote-response": await this.handleVoteResponse(message); break; case "append-entries": await this.handleAppendEntries(message); break; case "append-response": await this.handleAppendResponse(message); break; } } /** * Start election process */ private async startElection(): Promise<void> { const electionStart = Date.now(); this.performance.electionsHeld++; this.becomeCandidate(); // Vote for ourselves this.state.votesReceived.add(this.nodeId); // Request votes from other nodes const requestVoteMessage: RaftMessage = { type: "request-vote", term: this.state.currentTerm, senderId: this.nodeId, candidateId: this.nodeId, lastLogIndex: this.state.log.length, lastLogTerm: this.state.log.length > 0 ? this.state.log[this.state.log.length - 1].term : 0, timestamp: new Date(), }; await this.broadcastMessage(requestVoteMessage); // Check if we have majority if (this.state.votesReceived.size >= this.minQuorum) { const electionTime = Date.now() - electionStart; this.updateElectionTime(electionTime); this.becomeLeader(); } } /** * Handle RequestVote RPC */ private async handleRequestVote(message: RaftMessage): Promise<void> { let voteGranted = false; // Don't vote if we already voted for someone else in this term if ( this.state.votedFor === null || this.state.votedFor === message.candidateId ) { // Check if candidate's log is at least as up-to-date as ours const ourLastLogIndex = this.state.log.length; const ourLastLogTerm = ourLastLogIndex > 0 ? this.state.log[ourLastLogIndex - 1].term : 0; const candidateLogUpToDate = message.lastLogTerm! > ourLastLogTerm || (message.lastLogTerm! === ourLastLogTerm && message.lastLogIndex! >= ourLastLogIndex); if (candidateLogUpToDate) { voteGranted = true; this.state.votedFor = message.candidateId!; this.resetElectionTimeout(); } } const response: RaftMessage = { type: "vote-response", term: this.state.currentTerm, senderId: this.nodeId, targetId: message.senderId, voteGranted: voteGranted, timestamp: new Date(), }; await this.sendMessage(message.senderId, response); } /** * Handle vote response */ private async handleVoteResponse(message: RaftMessage): Promise<void> { if ( this.currentState !== "candidate" || message.term !== this.state.currentTerm ) { return; } if (message.voteGranted) { this.state.votesReceived.add(message.senderId); // Check if we have majority if (this.state.votesReceived.size >= this.minQuorum) { this.becomeLeader(); } } } /** * Handle AppendEntries RPC */ private async handleAppendEntries(message: RaftMessage): Promise<void> { let success = false; let matchIndex = 0; // Reset election timeout - we received heartbeat/entries from leader this.resetElectionTimeout(); if (message.term >= this.state.currentTerm) { this.becomeFollower(); // Check if log matches if ( message.prevLogIndex === 0 || (this.state.log.length >= message.prevLogIndex! && this.state.log[message.prevLogIndex! - 1].term === message.prevLogTerm) ) { success = true; // Append new entries if (message.entries && message.entries.length > 0) { // Remove conflicting entries this.state.log = this.state.log.slice(0, message.prevLogIndex!); // Append new entries this.state.log.push(...message.entries); matchIndex = this.state.log.length; } // Update commit index if (message.leaderCommit! > this.state.commitIndex) { this.state.commitIndex = Math.min( message.leaderCommit!, this.state.log.length, ); this.applyLogEntries(); } } } const response: RaftMessage = { type: "append-response", term: this.state.currentTerm, senderId: this.nodeId, targetId: message.senderId, success: success, matchIndex: matchIndex, timestamp: new Date(), }; await this.sendMessage(message.senderId, response); } /** * Handle append entries response */ private async handleAppendResponse(message: RaftMessage): Promise<void> { if ( this.currentState !== "leader" || message.term !== this.state.currentTerm ) { return; } const nodeId = message.senderId; if (message.success) { // Update follower's indices this.state.nextIndex.set(nodeId, message.matchIndex! + 1); this.state.matchIndex.set(nodeId, message.matchIndex!); // Check if we can commit more entries this.updateCommitIndex(); } else { // Decrement nextIndex and retry const currentNext = this.state.nextIndex.get(nodeId) || 1; this.state.nextIndex.set(nodeId, Math.max(1, currentNext - 1)); // Retry sending entries await this.sendAppendEntries(nodeId); } } /** * Replicate log entry to followers */ private async replicateEntry(entry: LogEntry): Promise<boolean> { if (this.currentState !== "leader") { return false; } const responses = new Set<string>(); responses.add(this.nodeId); // Leader counts as success // Send to all followers const promises: Promise<void>[] = []; for (const nodeId of this.nodes.keys()) { if (nodeId !== this.nodeId) { promises.push(this.sendAppendEntries(nodeId)); } } await Promise.all(promises); // Wait for majority to respond successfully return new Promise((resolve) => { const checkCommit = () => { const committed = Array.from(this.state.matchIndex.values()).filter( (index) => index >= entry.index, ).length + 1; // +1 for leader if (committed >= this.minQuorum) { resolve(true); } }; this.on("append-success", checkCommit); // Timeout after reasonable time setTimeout(() => { this.off("append-success", checkCommit); resolve(false); }, 1000); checkCommit(); // Initial check }); } /** * Send AppendEntries to a specific node */ private async sendAppendEntries(nodeId: string): Promise<void> { const nextIndex = this.state.nextIndex.get(nodeId) || 1; const prevLogIndex = nextIndex - 1; const prevLogTerm = prevLogIndex > 0 ? this.state.log[prevLogIndex - 1].term : 0; const entries = this.state.log.slice(nextIndex - 1); const message: RaftMessage = { type: "append-entries", term: this.state.currentTerm, senderId: this.nodeId, targetId: nodeId, leaderId: this.nodeId, prevLogIndex: prevLogIndex, prevLogTerm: prevLogTerm, entries: entries, leaderCommit: this.state.commitIndex, timestamp: new Date(), }; await this.sendMessage(nodeId, message); } /** * Update commit index based on majority replication */ private updateCommitIndex(): void { if (this.currentState !== "leader") { return; } // Find highest index replicated on majority const indices = [this.state.log.length]; // Leader's log length for (const matchIndex of this.state.matchIndex.values()) { indices.push(matchIndex); } indices.sort((a, b) => b - a); // Get the index that majority has replicated const majorityIndex = indices[this.minQuorum - 1]; if ( majorityIndex > this.state.commitIndex && majorityIndex <= this.state.log.length && this.state.log[majorityIndex - 1].term === this.state.currentTerm ) { this.state.commitIndex = majorityIndex; this.applyLogEntries(); // Mark entries as committed for (let i = 0; i < majorityIndex; i++) { this.state.log[i].committed = true; } } } /** * Apply committed log entries */ private applyLogEntries(): void { while (this.state.lastApplied < this.state.commitIndex) { this.state.lastApplied++; const entry = this.state.log[this.state.lastApplied - 1]; this.emit("command-applied", { command: entry.command, index: entry.index, }); this.performance.logEntriesApplied++; } } /** * Transition to follower state */ private becomeFollower(): void { if (this.currentState === "leader") { this.performance.leadershipDuration += Date.now() - (this.state as any).leaderStartTime || 0; } this.currentState = "follower"; this.clearHeartbeatTimer(); this.resetElectionTimeout(); this.emit("state-changed", "follower"); } /** * Transition to candidate state */ private becomeCandidate(): void { this.currentState = "candidate"; this.state.currentTerm++; this.state.votedFor = this.nodeId; this.state.votesReceived.clear(); this.clearElectionTimer(); this.resetElectionTimeout(); this.emit("state-changed", "candidate"); } /** * Transition to leader state */ private becomeLeader(): void { this.currentState = "leader"; (this.state as any).leaderStartTime = Date.now(); // Initialize leader state for (const nodeId of this.nodes.keys()) { this.state.nextIndex.set(nodeId, this.state.log.length + 1); this.state.matchIndex.set(nodeId, 0); } this.clearElectionTimer(); this.startHeartbeat(); this.emit("state-changed", "leader"); this.emit("leader-elected", this.nodeId); } /** * Start sending heartbeats (leader only) */ private startHeartbeat(): void { this.heartbeatTimer = setInterval(() => { this.sendHeartbeats(); }, this.heartbeatInterval); } /** * Send heartbeat to all followers */ private async sendHeartbeats(): Promise<void> { if (this.currentState !== "leader") { return; } for (const nodeId of this.nodes.keys()) { if (nodeId !== this.nodeId) { await this.sendAppendEntries(nodeId); } } } /** * Start election timeout */ private startElectionTimeout(): void { this.electionTimer = setTimeout(() => { if (this.currentState !== "leader") { this.startElection(); } }, this.state.electionTimeout); } /** * Reset election timeout */ private resetElectionTimeout(): void { this.clearElectionTimer(); this.state.electionTimeout = this.randomElectionTimeout(); this.startElectionTimeout(); } /** * Generate random election timeout */ private randomElectionTimeout(): number { return ( Math.floor( Math.random() * (this.electionTimeoutMax - this.electionTimeoutMin), ) + this.electionTimeoutMin ); } /** * Clear election timer */ private clearElectionTimer(): void { if (this.electionTimer) { clearTimeout(this.electionTimer); this.electionTimer = null; } } /** * Clear heartbeat timer */ private clearHeartbeatTimer(): void { if (this.heartbeatTimer) { clearInterval(this.heartbeatTimer); this.heartbeatTimer = null; } } /** * Update election time statistics */ private updateElectionTime(electionTime: number): void { if (this.performance.electionsHeld === 1) { this.performance.averageElectionTime = electionTime; } else { this.performance.averageElectionTime = (this.performance.averageElectionTime * (this.performance.electionsHeld - 1) + electionTime) / this.performance.electionsHeld; } } /** * Send message to specific node */ private async sendMessage( nodeId: string, message: RaftMessage, ): Promise<void> { // Simulate network communication this.emit("send-message", { nodeId, message }); } /** * Broadcast message to all nodes */ private async broadcastMessage(message: RaftMessage): Promise<void> { for (const nodeId of this.nodes.keys()) { if (nodeId !== this.nodeId) { await this.sendMessage(nodeId, message); } } } /** * Get minimum quorum size for Raft consensus */ public getMinQuorum(): number { return this.minQuorum; } /** * Check if we have sufficient nodes for quorum */ public hasQuorum(): boolean { const activeNodes = Array.from(this.nodes.values()).filter((node) => node.isActive).length + 1; // +1 for self return activeNodes >= this.minQuorum; } /** * Get current state */ public getCurrentState(): "follower" | "candidate" | "leader" { return this.currentState; } /** * Get current term */ public getCurrentTerm(): number { return this.state.currentTerm; } /** * Get current leader */ public getCurrentLeader(): string | null { if (this.currentState === "leader") { return this.nodeId; } // In a real implementation, we'd track the current leader return null; } /** * Get log entries */ public getLog(): LogEntry[] { return [...this.state.log]; } /** * Get performance metrics */ public getPerformanceMetrics(): typeof this.performance { return { ...this.performance }; } /** * Get Raft state information */ public getRaftState(): { nodeId: string; state: string; term: number; logLength: number; commitIndex: number; lastApplied: number; quorumSize: number; hasQuorum: boolean; } { return { nodeId: this.nodeId, state: this.currentState, term: this.state.currentTerm, logLength: this.state.log.length, commitIndex: this.state.commitIndex, lastApplied: this.state.lastApplied, quorumSize: this.minQuorum, hasQuorum: this.hasQuorum(), }; } /** * Shutdown the Raft node */ public shutdown(): void { this.clearElectionTimer(); this.clearHeartbeatTimer(); this.currentState = "follower"; this.emit("shutdown"); } } export default RaftConsensus;