@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.
1,108 lines (922 loc) • 36.6 kB
text/typescript
/**
* Comprehensive Test Suite for Byzantine Consensus
* Tests quorum configuration, fault tolerance, and consensus mechanisms
*/
import {
ByzantineConsensus,
Agent,
ConsensusMessage,
ConsensusProposal,
ConsensusState,
} from "../byzantine-consensus.js";
describe("ByzantineConsensus", () => {
let consensus: ByzantineConsensus;
let testAgents: Agent[];
beforeEach(() => {
consensus = new ByzantineConsensus("test-agent", 4);
// Setup test agents
testAgents = [
{
id: "agent-1",
publicKey: "key1",
isLeader: false,
reputation: 1.0,
lastActiveTime: new Date(),
},
{
id: "agent-2",
publicKey: "key2",
isLeader: false,
reputation: 0.9,
lastActiveTime: new Date(),
},
{
id: "agent-3",
publicKey: "key3",
isLeader: false,
reputation: 0.8,
lastActiveTime: new Date(),
},
{
id: "malicious-agent",
publicKey: "key4",
isLeader: false,
isMalicious: true,
reputation: 0.5,
lastActiveTime: new Date(),
},
];
// Register agents
testAgents.forEach((agent) => consensus.registerAgent(agent));
});
afterEach(() => {
consensus?.removeAllListeners();
});
describe("Initialization and Configuration", () => {
it("should initialize with correct quorum parameters", () => {
const state = consensus.getState();
expect(state.currentView).toBe(0);
expect(state.sequenceNumber).toBe(0);
expect(state.phase).toBe("pre-prepare");
expect(state.activeAgents.size).toBe(4);
});
it("should calculate fault threshold correctly", () => {
// For 4 agents, fault threshold should be floor((4-1)/3) = 1
const consensus4 = new ByzantineConsensus("test", 4);
expect(consensus4.canReachConsensus()).toBe(true);
// For 7 agents, fault threshold should be floor((7-1)/3) = 2
const consensus7 = new ByzantineConsensus("test", 7);
expect(consensus7.canReachConsensus()).toBe(true);
// For 10 agents, fault threshold should be floor((10-1)/3) = 3
const consensus10 = new ByzantineConsensus("test", 10);
expect(consensus10.canReachConsensus()).toBe(true);
});
it("should handle minimum node requirements for Byzantine fault tolerance", () => {
// Need at least 3f + 1 nodes to tolerate f faults
const consensus1 = new ByzantineConsensus("test", 1);
expect(consensus1.canReachConsensus()).toBe(false);
const consensus3 = new ByzantineConsensus("test", 3);
expect(consensus3.canReachConsensus()).toBe(false); // No agents registered
const consensus4 = new ByzantineConsensus("test", 4);
// Register enough honest agents
for (let i = 0; i < 4; i++) {
consensus4.registerAgent({
id: `agent-${i}`,
publicKey: `key${i}`,
isLeader: false,
reputation: 1.0,
lastActiveTime: new Date(),
});
}
expect(consensus4.canReachConsensus()).toBe(true);
});
it("should select leader correctly using round-robin", () => {
const state = consensus.getState();
// Initial leader should be first agent (view 0 % 4 = 0)
const activeAgents = Array.from(state.activeAgents);
expect(state.leader).toBe(activeAgents[0]);
});
});
describe("Quorum Configuration", () => {
describe("Quorum Size Calculations", () => {
it("should calculate correct quorum sizes for different network sizes", () => {
const testCases = [
{ totalNodes: 4, expectedQuorum: 2, faultTolerance: 1 },
{ totalNodes: 7, expectedQuorum: 4, faultTolerance: 2 },
{ totalNodes: 10, expectedQuorum: 6, faultTolerance: 3 },
{ totalNodes: 13, expectedQuorum: 8, faultTolerance: 4 },
];
testCases.forEach(({ totalNodes, expectedQuorum, faultTolerance }) => {
const testConsensus = new ByzantineConsensus("test", totalNodes);
// Quorum size is 2f for prepare/commit phases in PBFT
const calculatedQuorum = 2 * Math.floor((totalNodes - 1) / 3);
expect(calculatedQuorum).toBe(expectedQuorum);
// Can tolerate up to f malicious nodes
expect(Math.floor((totalNodes - 1) / 3)).toBe(faultTolerance);
});
});
it("should validate quorum requirements for consensus phases", async () => {
const proposal: ConsensusProposal = {
id: "test-proposal",
content: { data: "test" },
proposerId: "agent-1",
timestamp: new Date(),
hash: "test-hash",
};
// Mock leader selection to make agent-1 the leader
const state = consensus.getState();
state.leader = "agent-1";
let prepareCount = 0;
let commitCount = 0;
consensus.on("broadcast-message", (message: ConsensusMessage) => {
if (message.type === "prepare") {
prepareCount++;
} else if (message.type === "commit") {
commitCount++;
}
});
// Start consensus - should require 2f responses (2 for 4 nodes)
const result = await consensus.startConsensus(proposal);
// In a real scenario, we'd need to simulate message handling
// For now, just verify the structure is correct
expect(result).toBeDefined();
});
it("should handle dynamic quorum adjustments", () => {
// Start with 4 agents
expect(consensus.canReachConsensus()).toBe(true);
// Remove an agent - should still work with 3 honest agents
consensus.removeAgent("malicious-agent");
expect(consensus.canReachConsensus()).toBe(true);
// Remove another agent - now only 2 agents, cannot reach consensus
consensus.removeAgent("agent-3");
expect(consensus.canReachConsensus()).toBe(false);
});
});
describe("Fault Tolerance Validation", () => {
it("should detect when network cannot tolerate faults", () => {
// With 4 total agents and 1 malicious, should still work
expect(consensus.canReachConsensus()).toBe(true);
// Add more malicious agents
consensus.registerAgent({
id: "malicious-2",
publicKey: "key5",
isLeader: false,
isMalicious: true,
reputation: 0.3,
lastActiveTime: new Date(),
});
// Now have 2 malicious out of 5 total - should fail
expect(consensus.canReachConsensus()).toBe(false);
});
it("should validate minimum network size requirements", () => {
const smallConsensus = new ByzantineConsensus("test", 2);
// Register agents
smallConsensus.registerAgent({
id: "agent-1",
publicKey: "key1",
isLeader: false,
reputation: 1.0,
lastActiveTime: new Date(),
});
smallConsensus.registerAgent({
id: "agent-2",
publicKey: "key2",
isLeader: false,
reputation: 1.0,
lastActiveTime: new Date(),
});
// Cannot reach Byzantine consensus with only 2 nodes
expect(smallConsensus.canReachConsensus()).toBe(false);
});
it("should handle edge cases in fault calculations", () => {
// Test boundary conditions
const testCases = [
{ nodes: 3, malicious: 0, canConsensus: false }, // 3 < 3*0 + 1, but still too small
{ nodes: 4, malicious: 1, canConsensus: true }, // 4 >= 3*1 + 1
{ nodes: 6, malicious: 1, canConsensus: true }, // 6 >= 3*1 + 1
{ nodes: 7, malicious: 2, canConsensus: true }, // 7 >= 3*2 + 1
{ nodes: 7, malicious: 3, canConsensus: false }, // 7 < 3*3 + 1
];
testCases.forEach(({ nodes, malicious, canConsensus }, index) => {
const testConsensus = new ByzantineConsensus(`test-${index}`, nodes);
// Register honest agents
for (let i = 0; i < nodes - malicious; i++) {
testConsensus.registerAgent({
id: `honest-${i}`,
publicKey: `key-${i}`,
isLeader: false,
reputation: 1.0,
lastActiveTime: new Date(),
});
}
// Register malicious agents
for (let i = 0; i < malicious; i++) {
testConsensus.registerAgent({
id: `malicious-${i}`,
publicKey: `bad-key-${i}`,
isLeader: false,
isMalicious: true,
reputation: 0.1,
lastActiveTime: new Date(),
});
}
expect(testConsensus.canReachConsensus()).toBe(canConsensus);
});
});
});
describe("Quorum Formation and Validation", () => {
it("should validate quorum formation for prepare phase", async () => {
const proposal: ConsensusProposal = {
id: "quorum-test",
content: { action: "test" },
proposerId: "agent-1",
timestamp: new Date(),
hash: "quorum-hash",
};
// Simulate prepare phase quorum collection
const requiredPrepareResponses = 2; // 2f for 4 nodes
let prepareResponses = 0;
// Mock the internal prepare collection mechanism
const collectPrepareResponses =
consensus["collectPrepareResponses"].bind(consensus);
// Simulate receiving prepare messages
const prepareMessage: ConsensusMessage = {
type: "prepare",
viewNumber: 0,
sequenceNumber: 1,
digest: proposal.hash,
payload: null,
timestamp: new Date(),
signature: "test-sig",
senderId: "agent-2",
};
// Add multiple prepare messages to reach quorum
const state = consensus.getState();
state.proposals.set(proposal.id, proposal);
state.messages.set(proposal.hash, [prepareMessage, prepareMessage]);
// Test quorum validation
expect(state.messages.get(proposal.hash)).toHaveLength(2);
});
it("should validate quorum formation for commit phase", async () => {
const proposal: ConsensusProposal = {
id: "commit-test",
content: { action: "commit" },
proposerId: "agent-1",
timestamp: new Date(),
hash: "commit-hash",
};
const requiredCommitResponses = 2; // 2f for 4 nodes
// Simulate commit phase messages
const commitMessages = [
{
type: "commit" as const,
viewNumber: 0,
sequenceNumber: 1,
digest: proposal.hash,
payload: null,
timestamp: new Date(),
signature: "sig1",
senderId: "agent-2",
},
{
type: "commit" as const,
viewNumber: 0,
sequenceNumber: 1,
digest: proposal.hash,
payload: null,
timestamp: new Date(),
signature: "sig2",
senderId: "agent-3",
},
];
const state = consensus.getState();
state.proposals.set(proposal.id, proposal);
state.messages.set(proposal.hash, commitMessages);
const commitCount = state.messages
.get(proposal.hash)!
.filter((m) => m.type === "commit" && m.viewNumber === 0).length;
expect(commitCount).toBe(requiredCommitResponses);
});
it("should handle partial quorum scenarios", () => {
const proposal: ConsensusProposal = {
id: "partial-test",
content: { action: "partial" },
proposerId: "agent-1",
timestamp: new Date(),
hash: "partial-hash",
};
// Only 1 prepare message (need 2 for quorum)
const partialMessages = [
{
type: "prepare" as const,
viewNumber: 0,
sequenceNumber: 1,
digest: proposal.hash,
payload: null,
timestamp: new Date(),
signature: "sig1",
senderId: "agent-2",
},
];
const state = consensus.getState();
state.proposals.set(proposal.id, proposal);
state.messages.set(proposal.hash, partialMessages);
const prepareCount = state.messages
.get(proposal.hash)!
.filter((m) => m.type === "prepare" && m.viewNumber === 0).length;
expect(prepareCount).toBeLessThan(2); // Insufficient for quorum
});
});
});
describe("Consensus Protocol Flow", () => {
describe("Three-Phase Commit Protocol", () => {
it("should execute complete consensus flow successfully", async () => {
const proposal: ConsensusProposal = {
id: "flow-test",
content: { operation: "update", value: 42 },
proposerId: "agent-1",
timestamp: new Date(),
hash: "flow-hash",
};
// Track phases
const phases: string[] = [];
consensus.on("broadcast-message", (message: ConsensusMessage) => {
phases.push(`${message.type}-sent`);
});
consensus.on(
"consensus-reached",
(acceptedProposal: ConsensusProposal) => {
phases.push("consensus-complete");
expect(acceptedProposal.id).toBe(proposal.id);
},
);
// Mock leader status
const state = consensus.getState();
state.leader = "test-agent";
// Start consensus
const result = await consensus.startConsensus(proposal);
// In a full implementation, we would simulate message exchanges
// For now, verify the proposal was added to state
expect(state.proposals.has(proposal.id)).toBe(true);
expect(state.sequenceNumber).toBe(1);
});
it("should handle pre-prepare phase correctly", async () => {
const proposal: ConsensusProposal = {
id: "pre-prepare-test",
content: { data: "pre-prepare" },
proposerId: "test-agent",
timestamp: new Date(),
hash: "pre-prepare-hash",
};
let prePrepareMessage: ConsensusMessage | null = null;
consensus.on("broadcast-message", (message: ConsensusMessage) => {
if (message.type === "pre-prepare") {
prePrepareMessage = message;
}
});
const state = consensus.getState();
state.leader = "test-agent";
// Start consensus to trigger pre-prepare
await consensus.startConsensus(proposal);
// Verify pre-prepare was sent
expect(prePrepareMessage).toBeDefined();
expect(prePrepareMessage!.type).toBe("pre-prepare");
expect(prePrepareMessage!.digest).toBe(proposal.hash);
expect(state.phase).toBe("pre-prepare");
});
it("should validate prepare phase responses", async () => {
const prepareMessage: ConsensusMessage = {
type: "prepare",
viewNumber: 0,
sequenceNumber: 1,
digest: "test-digest",
payload: null,
timestamp: new Date(),
signature: "test-signature",
senderId: "agent-2",
};
let prepareHandled = false;
consensus.on("message-received", () => {
prepareHandled = true;
});
await consensus.processMessage(prepareMessage);
expect(prepareHandled).toBe(true);
const state = consensus.getState();
const messages = state.messages.get("test-digest") || [];
expect(messages.some((m) => m.type === "prepare")).toBe(true);
});
it("should validate commit phase responses", async () => {
const commitMessage: ConsensusMessage = {
type: "commit",
viewNumber: 0,
sequenceNumber: 1,
digest: "commit-digest",
payload: null,
timestamp: new Date(),
signature: "commit-signature",
senderId: "agent-3",
};
await consensus.processMessage(commitMessage);
const state = consensus.getState();
const messages = state.messages.get("commit-digest") || [];
expect(messages.some((m) => m.type === "commit")).toBe(true);
});
});
describe("Message Validation and Security", () => {
it("should validate message authenticity", async () => {
const validMessage: ConsensusMessage = {
type: "prepare",
viewNumber: 0,
sequenceNumber: 1,
digest: "valid-digest",
payload: null,
timestamp: new Date(),
signature: "valid-signature",
senderId: "agent-1",
};
const invalidMessage: ConsensusMessage = {
type: "prepare",
viewNumber: 0,
sequenceNumber: 1,
digest: "invalid-digest",
payload: null,
timestamp: new Date(),
signature: "", // Invalid signature
senderId: "unknown-agent", // Unknown sender
};
let invalidMessageCount = 0;
consensus.on("invalid-message", () => {
invalidMessageCount++;
});
await consensus.processMessage(validMessage);
await consensus.processMessage(invalidMessage);
expect(invalidMessageCount).toBe(1);
});
it("should detect malicious behavior", async () => {
const maliciousMessage: ConsensusMessage = {
type: "pre-prepare",
viewNumber: 0,
sequenceNumber: 1,
digest: "malicious-digest",
payload: null,
timestamp: new Date(),
signature: "malicious-signature",
senderId: "agent-2", // Not the leader
};
let maliciousBehaviorDetected = false;
consensus.on("malicious-behavior", (event) => {
maliciousBehaviorDetected = true;
expect(event.type).toBe("unauthorized-pre-prepare");
expect(event.agentId).toBe("agent-2");
});
await consensus.processMessage(maliciousMessage);
expect(maliciousBehaviorDetected).toBe(true);
});
it("should filter messages from malicious agents", async () => {
const messageFromMalicious: ConsensusMessage = {
type: "prepare",
viewNumber: 0,
sequenceNumber: 1,
digest: "mal-digest",
payload: null,
timestamp: new Date(),
signature: "mal-signature",
senderId: "malicious-agent",
};
let processedCount = 0;
consensus.on("message-received", () => {
processedCount++;
});
await consensus.processMessage(messageFromMalicious);
// Message should be rejected due to malicious sender
expect(processedCount).toBe(0);
});
});
});
describe("View Change and Leader Election", () => {
describe("View Change Mechanism", () => {
it("should initiate view change on consensus failure", async () => {
let viewChangeInitiated = false;
consensus.on("view-change-initiated", (newView) => {
viewChangeInitiated = true;
expect(newView).toBe(1);
});
const initialState = consensus.getState();
const initialView = initialState.currentView;
// Trigger view change manually
await (consensus as any).initiateViewChange();
expect(viewChangeInitiated).toBe(true);
const newState = consensus.getState();
expect(newState.currentView).toBe(initialView + 1);
});
it("should elect new leader during view change", async () => {
const initialState = consensus.getState();
const initialLeader = initialState.leader;
await (consensus as any).initiateViewChange();
const newState = consensus.getState();
const newLeader = newState.leader;
// Leader should change in round-robin fashion
expect(newLeader).not.toBe(initialLeader);
});
it("should handle view change messages correctly", async () => {
const viewChangeMessage: ConsensusMessage = {
type: "view-change",
viewNumber: 1,
sequenceNumber: 0,
digest: "",
payload: {
lastCommitted: [],
messageLog: [],
},
timestamp: new Date(),
signature: "vc-signature",
senderId: "agent-2",
};
// Need multiple view change messages for quorum
const messages = Array.from({ length: 3 }, (_, i) => ({
...viewChangeMessage,
senderId: `agent-${i + 1}`,
}));
// Process view change messages
for (const message of messages) {
await consensus.processMessage(message);
}
// Should trigger new view if this agent is the new leader
const state = consensus.getState();
expect(state.currentView).toBeGreaterThanOrEqual(1);
});
it("should validate new view messages", async () => {
const newViewMessage: ConsensusMessage = {
type: "new-view",
viewNumber: 1,
sequenceNumber: 0,
digest: "",
payload: {
viewChangeMessages: [],
},
timestamp: new Date(),
signature: "nv-signature",
senderId: "agent-1",
};
let newViewAccepted = false;
consensus.on("new-view-accepted", (viewNumber) => {
newViewAccepted = true;
expect(viewNumber).toBe(1);
});
// Mock that agent-1 is the new leader for view 1
jest.spyOn(consensus as any, "selectLeader").mockReturnValue("agent-1");
await consensus.processMessage(newViewMessage);
expect(newViewAccepted).toBe(true);
});
});
describe("Leader Election Algorithm", () => {
it("should select leader deterministically", () => {
const selectLeader = (consensus as any).selectLeader.bind(consensus);
// Should always return same leader for same view
const leader1 = selectLeader(0);
const leader2 = selectLeader(0);
expect(leader1).toBe(leader2);
// Different views should potentially have different leaders
const leader3 = selectLeader(1);
expect(typeof leader3).toBe("string");
});
it("should rotate leadership fairly", () => {
const selectLeader = (consensus as any).selectLeader.bind(consensus);
const state = consensus.getState();
const agentCount = state.activeAgents.size;
const leaders = new Set();
for (let view = 0; view < agentCount * 2; view++) {
leaders.add(selectLeader(view));
}
// Should have used all agents as leaders over multiple rounds
expect(leaders.size).toBe(agentCount);
});
it("should handle leadership validation", () => {
const state = consensus.getState();
const originalLeader = state.leader;
// Test leader validation
const isLeader1 = (consensus as any).isLeader();
// Change leader
state.leader = "test-agent";
const isLeader2 = (consensus as any).isLeader();
// Restore original
state.leader = originalLeader;
const isLeader3 = (consensus as any).isLeader();
expect(isLeader2).toBe(true);
});
});
});
describe("Network Partitions and Recovery", () => {
describe("Partition Simulation", () => {
it("should handle network partition gracefully", () => {
const initialAgentCount = consensus.getState().activeAgents.size;
// Simulate partition by removing agents
consensus.simulatePartition(["agent-1", "agent-2"]);
const state = consensus.getState();
expect(state.activeAgents.size).toBe(initialAgentCount - 2);
expect(state.activeAgents.has("agent-1")).toBe(false);
expect(state.activeAgents.has("agent-2")).toBe(false);
});
it("should detect when consensus becomes impossible due to partition", () => {
// Start with 4 agents, can tolerate 1 fault
expect(consensus.canReachConsensus()).toBe(true);
// Partition 2 agents - now only 2 remain, cannot reach consensus
consensus.simulatePartition(["agent-1", "agent-2"]);
expect(consensus.canReachConsensus()).toBe(false);
});
it("should recover from partition when network heals", () => {
// Simulate partition
consensus.simulatePartition(["agent-1", "agent-2"]);
expect(consensus.canReachConsensus()).toBe(false);
// Heal partition
consensus.healPartition(["agent-1", "agent-2"]);
const state = consensus.getState();
expect(state.activeAgents.has("agent-1")).toBe(true);
expect(state.activeAgents.has("agent-2")).toBe(true);
expect(consensus.canReachConsensus()).toBe(true);
});
it("should emit appropriate events for partition scenarios", () => {
let partitionEvent: string[] = [];
let healEvent: string[] = [];
consensus.on("network-partition", (agentIds) => {
partitionEvent = agentIds;
});
consensus.on("network-healed", (agentIds) => {
healEvent = agentIds;
});
const partitionedAgents = ["agent-1", "agent-3"];
consensus.simulatePartition(partitionedAgents);
expect(partitionEvent).toEqual(partitionedAgents);
consensus.healPartition(partitionedAgents);
expect(healEvent).toEqual(partitionedAgents);
});
});
describe("Partition Recovery Scenarios", () => {
it("should handle majority partition scenarios", () => {
// With 4 agents, partition into 3 vs 1
consensus.simulatePartition(["agent-1"]); // Remove 1, keep 3
// Majority partition (3 agents) should be able to continue
expect(consensus.canReachConsensus()).toBe(true);
});
it("should handle minority partition scenarios", () => {
// Partition into 2 vs 2 (both are minorities for Byzantine consensus)
consensus.simulatePartition(["agent-1", "agent-2"]); // Remove 2, keep 2
// Neither partition can reach consensus
expect(consensus.canReachConsensus()).toBe(false);
});
it("should handle agents rejoining after partition", () => {
const agent1Data = testAgents.find((a) => a.id === "agent-1")!;
// Remove agent
consensus.removeAgent("agent-1");
expect(consensus.getState().activeAgents.has("agent-1")).toBe(false);
// Re-register agent (simulating rejoin)
consensus.registerAgent(agent1Data);
expect(consensus.getState().activeAgents.has("agent-1")).toBe(true);
});
});
});
describe("Performance Metrics and Monitoring", () => {
describe("Consensus Performance Tracking", () => {
it("should track consensus round metrics", () => {
const initialMetrics = consensus.getPerformanceMetrics();
expect(initialMetrics.consensusRounds).toBe(0);
expect(initialMetrics.averageLatency).toBe(0);
expect(initialMetrics.successRate).toBe(0);
});
it("should update performance metrics after consensus", async () => {
// Mock successful consensus
const updatePerformance = (consensus as any).updatePerformance.bind(
consensus,
);
const startTime = Date.now() - 1000; // 1 second ago
updatePerformance(startTime, true);
const metrics = consensus.getPerformanceMetrics();
expect(metrics.consensusRounds).toBe(1);
expect(metrics.averageLatency).toBeGreaterThan(0);
expect(metrics.successRate).toBe(1.0);
});
it("should track fault detection metrics", async () => {
const maliciousMessage: ConsensusMessage = {
type: "prepare",
viewNumber: 0,
sequenceNumber: 1,
digest: "test-digest",
payload: null,
timestamp: new Date(),
signature: "invalid-signature",
senderId: "malicious-agent",
};
await consensus.processMessage(maliciousMessage);
const metrics = consensus.getPerformanceMetrics();
expect(metrics.faultsDetected).toBeGreaterThan(0);
});
it("should calculate success rates accurately", async () => {
const updatePerformance = (consensus as any).updatePerformance.bind(
consensus,
);
// Simulate mixed results
updatePerformance(Date.now(), true); // Success
updatePerformance(Date.now(), false); // Failure
updatePerformance(Date.now(), true); // Success
const metrics = consensus.getPerformanceMetrics();
expect(metrics.consensusRounds).toBe(3);
expect(metrics.successRate).toBeCloseTo(2 / 3);
});
});
describe("Latency and Throughput Metrics", () => {
it("should measure consensus latency correctly", async () => {
const updatePerformance = (consensus as any).updatePerformance.bind(
consensus,
);
// Simulate different latencies
const baseTime = Date.now();
updatePerformance(baseTime - 100, true); // 100ms
updatePerformance(baseTime - 200, true); // 200ms
const metrics = consensus.getPerformanceMetrics();
expect(metrics.averageLatency).toBe(150); // (100 + 200) / 2
});
it("should track consensus throughput over time", () => {
const updatePerformance = (consensus as any).updatePerformance.bind(
consensus,
);
// Simulate multiple consensus rounds
for (let i = 0; i < 10; i++) {
updatePerformance(Date.now() - 100, true);
}
const metrics = consensus.getPerformanceMetrics();
expect(metrics.consensusRounds).toBe(10);
expect(metrics.successRate).toBe(1.0);
});
});
});
describe("Edge Cases and Error Handling", () => {
describe("Malicious Behavior Detection", () => {
it("should detect double-spend attempts", async () => {
// Simulate conflicting proposals from same agent
const proposal1: ConsensusProposal = {
id: "conflict-1",
content: { account: "A", balance: -100 },
proposerId: "agent-1",
timestamp: new Date(),
hash: "hash1",
};
const proposal2: ConsensusProposal = {
id: "conflict-2",
content: { account: "A", balance: -150 },
proposerId: "agent-1",
timestamp: new Date(),
hash: "hash2",
};
// Both proposals from same agent in same view should be suspicious
const state = consensus.getState();
state.leader = "agent-1";
// In real implementation, this would detect conflicting proposals
expect(proposal1.proposerId).toBe(proposal2.proposerId);
});
it("should handle message replay attacks", async () => {
const message: ConsensusMessage = {
type: "prepare",
viewNumber: 0,
sequenceNumber: 1,
digest: "replay-digest",
payload: null,
timestamp: new Date(Date.now() - 60000), // 1 minute old
signature: "replay-signature",
senderId: "agent-2",
};
// Process same message twice
await consensus.processMessage(message);
await consensus.processMessage(message);
// Should only be processed once (duplicate detection)
const state = consensus.getState();
const messages = state.messages.get("replay-digest") || [];
// In a full implementation, would check for duplicate filtering
expect(messages.length).toBeGreaterThanOrEqual(1);
});
it("should validate message timing", async () => {
const oldMessage: ConsensusMessage = {
type: "prepare",
viewNumber: 0,
sequenceNumber: 1,
digest: "old-digest",
payload: null,
timestamp: new Date(Date.now() - 3600000), // 1 hour old
signature: "old-signature",
senderId: "agent-2",
};
const futureMessage: ConsensusMessage = {
type: "prepare",
viewNumber: 0,
sequenceNumber: 1,
digest: "future-digest",
payload: null,
timestamp: new Date(Date.now() + 3600000), // 1 hour in future
signature: "future-signature",
senderId: "agent-2",
};
// In a full implementation, would validate timestamp bounds
await consensus.processMessage(oldMessage);
await consensus.processMessage(futureMessage);
// Should handle timing validation appropriately
expect(true).toBe(true); // Placeholder
});
});
describe("Resource Exhaustion Scenarios", () => {
it("should handle large numbers of concurrent proposals", async () => {
const proposals = Array.from({ length: 100 }, (_, i) => ({
id: `proposal-${i}`,
content: { data: `data-${i}` },
proposerId: "test-agent",
timestamp: new Date(),
hash: `hash-${i}`,
}));
const state = consensus.getState();
state.leader = "test-agent";
// Add all proposals
proposals.forEach((proposal) => {
state.proposals.set(proposal.id, proposal);
});
expect(state.proposals.size).toBe(100);
});
it("should handle message flooding", async () => {
const messages = Array.from({ length: 1000 }, (_, i) => ({
type: "prepare" as const,
viewNumber: 0,
sequenceNumber: i,
digest: `digest-${i}`,
payload: null,
timestamp: new Date(),
signature: `sig-${i}`,
senderId: "agent-2",
}));
let processedCount = 0;
consensus.on("message-received", () => {
processedCount++;
});
// Process all messages
for (const message of messages.slice(0, 10)) {
// Limit for test performance
await consensus.processMessage(message);
}
// Should handle gracefully without crashing
expect(processedCount).toBeLessThanOrEqual(10);
});
});
describe("State Consistency Validation", () => {
it("should maintain consistent state across operations", async () => {
const initialState = consensus.getState();
const initialView = initialState.currentView;
const initialSequence = initialState.sequenceNumber;
// Perform various operations
consensus.registerAgent({
id: "new-agent",
publicKey: "new-key",
isLeader: false,
reputation: 1.0,
lastActiveTime: new Date(),
});
await (consensus as any).initiateViewChange();
// State should remain consistent
const finalState = consensus.getState();
expect(finalState.currentView).toBeGreaterThan(initialView);
expect(finalState.sequenceNumber).toBeGreaterThanOrEqual(
initialSequence,
);
});
it("should validate state transitions", () => {
const state = consensus.getState();
const validPhases = ["pre-prepare", "prepare", "commit", "view-change"];
expect(validPhases).toContain(state.phase);
expect(state.currentView).toBeGreaterThanOrEqual(0);
expect(state.sequenceNumber).toBeGreaterThanOrEqual(0);
});
it("should handle concurrent state modifications safely", async () => {
// Simulate concurrent operations
const operations = [
() =>
consensus.registerAgent({
id: "concurrent-1",
publicKey: "key1",
isLeader: false,
reputation: 1.0,
lastActiveTime: new Date(),
}),
() =>
consensus.registerAgent({
id: "concurrent-2",
publicKey: "key2",
isLeader: false,
reputation: 1.0,
lastActiveTime: new Date(),
}),
() => consensus.removeAgent("agent-1"),
];
// Execute concurrently
await Promise.all(operations.map((op) => op()));
// State should remain consistent
const state = consensus.getState();
expect(state.activeAgents.size).toBeGreaterThan(0);
});
});
});
});